diff --git a/.github/workflows/master-build.yml b/.github/workflows/master-build.yml
index bd72f85e5..a99a08553 100644
--- a/.github/workflows/master-build.yml
+++ b/.github/workflows/master-build.yml
@@ -31,7 +31,7 @@ jobs:
strategy:
matrix:
os: [ ubuntu-latest, windows-latest, macos-latest ]
- java: [ '11', '21' ]
+ java: [ '21' ]
steps:
- uses: actions/checkout@v2
diff --git a/builtins/pom.xml b/builtins/pom.xml
index fc9e4bfdc..33d2efd2b 100644
--- a/builtins/pom.xml
+++ b/builtins/pom.xml
@@ -62,34 +62,12 @@
default-compile
-
- **/ConsoleEngineImpl.java
- **/TTop.java
-
-Xlint:all,-options
-Werror
-
- compact
-
- compile
-
-
-
- **/ConsoleEngineImpl.java
- **/TTop.java
-
-
- -Xlint:all,-options
- -Werror
- -profile
- compact1
-
-
-
diff --git a/console/pom.xml b/console/pom.xml
index a3a53c2d1..c0d0ff3eb 100644
--- a/console/pom.xml
+++ b/console/pom.xml
@@ -62,34 +62,12 @@
default-compile
-
- **/ConsoleEngineImpl.java
- **/TTop.java
-
-Xlint:all,-options
-Werror
-
- compact
-
- compile
-
-
-
- **/ConsoleEngineImpl.java
- **/TTop.java
-
-
- -Xlint:all,-options
- -Werror
- -profile
- compact1
-
-
-
diff --git a/demo/jline-gogo.bat b/demo/jline-gogo.bat
index 3acd60ec7..127489835 100755
--- a/demo/jline-gogo.bat
+++ b/demo/jline-gogo.bat
@@ -36,6 +36,7 @@ set "logconf=%DIRNAME%etc\logging.properties"
if "%1" == "debug" goto :EXECUTE_DEBUG
if "%1" == "debugs" goto :EXECUTE_DEBUGS
if "%1" == "verbose" goto :EXECUTE_VERBOSE
+ if "%1" == "ffm" goto :EXECUTE_FFM
if "%1" == "" goto :EXECUTE_MAIN
set "opts=%opts% %~1"
shift
@@ -76,6 +77,11 @@ set "logconf=%DIRNAME%etc\logging.properties"
shift
goto :RUN_LOOP
+:EXECUTE_FFM
+ set "opts=%opts% --enable-preview --enable-native-access=ALL-UNNAMED"
+ shift
+ goto :RUN_LOOP
+
:EXECUTE_MAIN
popd
diff --git a/demo/jline-gogo.sh b/demo/jline-gogo.sh
index 1444171d3..c6c8325b1 100755
--- a/demo/jline-gogo.sh
+++ b/demo/jline-gogo.sh
@@ -65,6 +65,10 @@ while [ "${1}" != "" ]; do
logconf="${DIRNAME}/etc/logging-verbose.properties"
shift
;;
+ 'ffm')
+ opts="${opts} --enable-preview --enable-native-access=ALL-UNNAMED"
+ shift
+ ;;
*)
opts="${opts} ${1}"
shift
@@ -98,6 +102,7 @@ echo "Launching Gogo JLine..."
echo "Classpath: $cp"
set mouse=a
java -cp $cp \
+ --enable-preview \
$opts \
-Dgosh.home="${DIRNAME}" \
-Djava.util.logging.config.file="${logconf}" \
diff --git a/demo/pom.xml b/demo/pom.xml
index b14abbded..9c7ca7eea 100644
--- a/demo/pom.xml
+++ b/demo/pom.xml
@@ -27,6 +27,10 @@
+
+ org.jline
+ jline-terminal-ffm
+
org.jline
jline-terminal-jansi
diff --git a/jline/pom.xml b/jline/pom.xml
index b2eedb4f8..c10bd7fa8 100644
--- a/jline/pom.xml
+++ b/jline/pom.xml
@@ -24,6 +24,7 @@
org.jline
+ --enable-preview --release 21
@@ -316,10 +317,9 @@
default-compile
-
- **/TTop.java
- **/ConsoleEngineImpl.java
-
+
+ **/ffm/*.java
+
-Xlint:all,-options
-Werror
@@ -327,20 +327,19 @@
- compact
+ jdk21
compile
-
- **/TTop.java
- **/ConsoleEngineImpl.java
-
+
+ **/ffm/*.java
+
+ 21
-Xlint:all,-options
-Werror
- -profile
- compact1
+ --enable-preview
diff --git a/native/src/main/java/org/jline/nativ/OSInfo.java b/native/src/main/java/org/jline/nativ/OSInfo.java
index 689f5b385..9df657e4f 100644
--- a/native/src/main/java/org/jline/nativ/OSInfo.java
+++ b/native/src/main/java/org/jline/nativ/OSInfo.java
@@ -48,7 +48,7 @@ public class OSInfo {
public static final String PPC64 = "ppc64";
public static final String ARM64 = "arm64";
- private static final HashMap archMapping = new HashMap();
+ private static final HashMap archMapping = new HashMap<>();
static {
// x86 mappings
@@ -116,6 +116,7 @@ public static boolean isAndroid() {
return System.getProperty("java.runtime.name", "").toLowerCase().contains("android");
}
+ @SuppressWarnings("unused")
public static boolean isAlpine() {
try {
Process p = Runtime.getRuntime().exec(new String[] {"cat", "/etc/os-release", "|", "grep", "^ID"});
diff --git a/pom.xml b/pom.xml
index 67de2bd1c..6e1e237ce 100644
--- a/pom.xml
+++ b/pom.xml
@@ -58,6 +58,7 @@
native
terminal
+ terminal-ffm
terminal-jna
terminal-jansi
reader
@@ -92,7 +93,7 @@
UTF-8
2023-03-08T19:46:51Z
- 11
+ 21
1.8
3.5.0
@@ -131,6 +132,12 @@
${project.version}
+
+ org.jline
+ jline-terminal-ffm
+ ${project.version}
+
+
org.jline
jline-terminal-jansi
@@ -407,8 +414,6 @@
-Xlint:all,-options,-processing
-Werror
- -profile
- compact1
true
diff --git a/reader/pom.xml b/reader/pom.xml
index f25c86abe..1eccc313f 100644
--- a/reader/pom.xml
+++ b/reader/pom.xml
@@ -24,6 +24,7 @@
org.jline.reader
+ --add-opens java.base/java.io=ALL-UNNAMED --enable-preview
diff --git a/reader/src/test/java/org/jline/reader/impl/LineReaderTest.java b/reader/src/test/java/org/jline/reader/impl/LineReaderTest.java
index 10fabd33c..584f433bb 100644
--- a/reader/src/test/java/org/jline/reader/impl/LineReaderTest.java
+++ b/reader/src/test/java/org/jline/reader/impl/LineReaderTest.java
@@ -13,6 +13,8 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
@@ -181,7 +183,9 @@ public void testPreferAppNameFromConstructor() throws IOException {
@Test
public void terminalLineInfiniteLoop() throws IOException {
- ByteArrayInputStream in = new ByteArrayInputStream("hello\nworld\n".getBytes(StandardCharsets.UTF_8));
+ PipedInputStream in = new PipedInputStream();
+ PipedOutputStream outIn = new PipedOutputStream(in);
+ outIn.write("hello\nworld\n".getBytes(StandardCharsets.UTF_8));
ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
Terminal terminal = TerminalBuilder.builder().streams(in, out).build();
diff --git a/reader/src/test/java/org/jline/terminal/impl/ExternalTerminalTest.java b/reader/src/test/java/org/jline/terminal/impl/ExternalTerminalTest.java
index 7573c5964..7d2e28557 100644
--- a/reader/src/test/java/org/jline/terminal/impl/ExternalTerminalTest.java
+++ b/reader/src/test/java/org/jline/terminal/impl/ExternalTerminalTest.java
@@ -29,6 +29,8 @@
import org.jline.terminal.Cursor;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@@ -39,6 +41,16 @@
public class ExternalTerminalTest {
+ @BeforeEach
+ public void setup() {
+ System.setProperty(TerminalBuilder.PROP_PROVIDERS, "exec");
+ }
+
+ @AfterEach
+ public void tearDown() {
+ System.clearProperty(TerminalBuilder.PROP_PROVIDERS);
+ }
+
@Test
public void testInput() throws IOException, InterruptedException {
PipedInputStream in = new PipedInputStream();
diff --git a/terminal-ffm/pom.xml b/terminal-ffm/pom.xml
new file mode 100644
index 000000000..5bf5e6ec8
--- /dev/null
+++ b/terminal-ffm/pom.xml
@@ -0,0 +1,75 @@
+
+
+
+
+ 4.0.0
+
+
+ org.jline
+ jline-parent
+ 3.23.1-SNAPSHOT
+
+
+ jline-terminal-ffm
+ JLine FFM Terminal
+
+
+ 21
+ org.jline.terminal.ffm
+
+
+
+
+ org.jline
+ jline-terminal
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+
+
+
+
+ org.apache.felix
+ maven-bundle-plugin
+
+
+ *;-noimport:=true
+ org.jline.terminal
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 21
+
+ --enable-preview
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ --enable-preview --enable-native-access=ALL-UNNAMED
+
+
+
+
+
+
diff --git a/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/CLibrary.java b/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/CLibrary.java
new file mode 100644
index 000000000..c694b6839
--- /dev/null
+++ b/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/CLibrary.java
@@ -0,0 +1,1075 @@
+/*
+ * Copyright (c) 2022-2023, the original author(s).
+ *
+ * This software is distributable under the BSD license. See the terms of the
+ * BSD license in the documentation provided with this software.
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+package org.jline.terminal.impl.ffm;
+
+import java.lang.foreign.*;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.VarHandle;
+import java.util.EnumMap;
+import java.util.EnumSet;
+
+import org.jline.terminal.Attributes;
+import org.jline.terminal.Size;
+import org.jline.terminal.spi.Pty;
+
+@SuppressWarnings("preview")
+class CLibrary {
+ // Window sizes.
+ // @see IOCTL_TTY(2) man-page
+ static class winsize {
+ static final GroupLayout LAYOUT;
+ private static final VarHandle ws_col;
+ private static final VarHandle ws_row;
+
+ static {
+ LAYOUT = MemoryLayout.structLayout(
+ ValueLayout.JAVA_SHORT.withName("ws_row"),
+ ValueLayout.JAVA_SHORT.withName("ws_col"),
+ ValueLayout.JAVA_SHORT,
+ ValueLayout.JAVA_SHORT);
+ ws_row = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("ws_row"));
+ ws_col = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("ws_col"));
+ }
+
+ private final java.lang.foreign.MemorySegment seg;
+
+ winsize() {
+ seg = java.lang.foreign.Arena.ofAuto().allocate(LAYOUT);
+ }
+
+ winsize(short ws_col, short ws_row) {
+ this();
+ ws_col(ws_col);
+ ws_row(ws_row);
+ }
+
+ java.lang.foreign.MemorySegment segment() {
+ return seg;
+ }
+
+ short ws_col() {
+ return (short) ws_col.get(seg);
+ }
+
+ void ws_col(short col) {
+ ws_col.set(seg, col);
+ }
+
+ short ws_row() {
+ return (short) ws_row.get(seg);
+ }
+
+ void ws_row(short row) {
+ ws_row.set(seg, row);
+ }
+ }
+
+ // termios structure for termios functions, describing a general terminal interface that is
+ // provided to control asynchronous communications ports
+ // @see TERMIOS(3) man-page
+ static class termios {
+ static final GroupLayout LAYOUT;
+ private static final VarHandle c_iflag;
+ private static final VarHandle c_oflag;
+ private static final VarHandle c_cflag;
+ private static final VarHandle c_lflag;
+ private static final VarHandle c_ispeed;
+ private static final VarHandle c_ospeed;
+
+ static {
+ LAYOUT = MemoryLayout.structLayout(
+ ValueLayout.JAVA_LONG.withName("c_iflag"),
+ ValueLayout.JAVA_LONG.withName("c_oflag"),
+ ValueLayout.JAVA_LONG.withName("c_cflag"),
+ ValueLayout.JAVA_LONG.withName("c_lflag"),
+ MemoryLayout.sequenceLayout(32, ValueLayout.JAVA_BYTE).withName("c_cc"),
+ ValueLayout.JAVA_LONG.withName("c_ispeed"),
+ ValueLayout.JAVA_LONG.withName("c_ospeed"));
+ c_iflag = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("c_iflag"));
+ c_oflag = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("c_oflag"));
+ c_cflag = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("c_cflag"));
+ c_lflag = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("c_lflag"));
+ c_ispeed = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("c_ispeed"));
+ c_ospeed = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("c_ospeed"));
+ }
+
+ private final java.lang.foreign.MemorySegment seg;
+
+ termios() {
+ seg = java.lang.foreign.Arena.ofAuto().allocate(LAYOUT);
+ }
+
+ termios(Attributes t) {
+ this();
+ // Input flags
+ long c_iflag = 0;
+ c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.IGNBRK), IGNBRK, c_iflag);
+ c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.BRKINT), BRKINT, c_iflag);
+ c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.IGNPAR), IGNPAR, c_iflag);
+ c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.PARMRK), PARMRK, c_iflag);
+ c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.INPCK), INPCK, c_iflag);
+ c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.ISTRIP), ISTRIP, c_iflag);
+ c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.INLCR), INLCR, c_iflag);
+ c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.IGNCR), IGNCR, c_iflag);
+ c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.ICRNL), ICRNL, c_iflag);
+ c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.IXON), IXON, c_iflag);
+ c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.IXOFF), IXOFF, c_iflag);
+ c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.IXANY), IXANY, c_iflag);
+ c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.IMAXBEL), IMAXBEL, c_iflag);
+ c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.IUTF8), IUTF8, c_iflag);
+ c_iflag(c_iflag);
+ // Output flags
+ long c_oflag = 0;
+ c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.OPOST), OPOST, c_oflag);
+ c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.ONLCR), ONLCR, c_oflag);
+ c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.OXTABS), OXTABS, c_oflag);
+ c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.ONOEOT), ONOEOT, c_oflag);
+ c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.OCRNL), OCRNL, c_oflag);
+ c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.ONOCR), ONOCR, c_oflag);
+ c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.ONLRET), ONLRET, c_oflag);
+ c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.OFILL), OFILL, c_oflag);
+ c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.NLDLY), NLDLY, c_oflag);
+ c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.TABDLY), TABDLY, c_oflag);
+ c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.CRDLY), CRDLY, c_oflag);
+ c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.FFDLY), FFDLY, c_oflag);
+ c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.BSDLY), BSDLY, c_oflag);
+ c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.VTDLY), VTDLY, c_oflag);
+ c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.OFDEL), OFDEL, c_oflag);
+ c_oflag(c_oflag);
+ // Control flags
+ long c_cflag = 0;
+ c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.CIGNORE), CIGNORE, c_cflag);
+ c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.CS5), CS5, c_cflag);
+ c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.CS6), CS6, c_cflag);
+ c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.CS7), CS7, c_cflag);
+ c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.CS8), CS8, c_cflag);
+ c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.CSTOPB), CSTOPB, c_cflag);
+ c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.CREAD), CREAD, c_cflag);
+ c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.PARENB), PARENB, c_cflag);
+ c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.PARODD), PARODD, c_cflag);
+ c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.HUPCL), HUPCL, c_cflag);
+ c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.CLOCAL), CLOCAL, c_cflag);
+ c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.CCTS_OFLOW), CCTS_OFLOW, c_cflag);
+ c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.CRTS_IFLOW), CRTS_IFLOW, c_cflag);
+ c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.CDTR_IFLOW), CDTR_IFLOW, c_cflag);
+ c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.CDSR_OFLOW), CDSR_OFLOW, c_cflag);
+ c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.CCAR_OFLOW), CCAR_OFLOW, c_cflag);
+ c_cflag(c_cflag);
+ // Local flags
+ long c_lflag = 0;
+ c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.ECHOKE), ECHOKE, c_lflag);
+ c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.ECHOE), ECHOE, c_lflag);
+ c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.ECHOK), ECHOK, c_lflag);
+ c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.ECHO), ECHO, c_lflag);
+ c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.ECHONL), ECHONL, c_lflag);
+ c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.ECHOPRT), ECHOPRT, c_lflag);
+ c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.ECHOCTL), ECHOCTL, c_lflag);
+ c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.ISIG), ISIG, c_lflag);
+ c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.ICANON), ICANON, c_lflag);
+ c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.ALTWERASE), ALTWERASE, c_lflag);
+ c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.IEXTEN), IEXTEN, c_lflag);
+ c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.EXTPROC), EXTPROC, c_lflag);
+ c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.TOSTOP), TOSTOP, c_lflag);
+ c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.FLUSHO), FLUSHO, c_lflag);
+ c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.NOKERNINFO), NOKERNINFO, c_lflag);
+ c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.PENDIN), PENDIN, c_lflag);
+ c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.NOFLSH), NOFLSH, c_lflag);
+ c_lflag(c_lflag);
+ // Control chars
+ byte[] c_cc = new byte[20];
+ c_cc[VEOF] = (byte) t.getControlChar(Attributes.ControlChar.VEOF);
+ c_cc[VEOL] = (byte) t.getControlChar(Attributes.ControlChar.VEOL);
+ c_cc[VEOL2] = (byte) t.getControlChar(Attributes.ControlChar.VEOL2);
+ c_cc[VERASE] = (byte) t.getControlChar(Attributes.ControlChar.VERASE);
+ c_cc[VWERASE] = (byte) t.getControlChar(Attributes.ControlChar.VWERASE);
+ c_cc[VKILL] = (byte) t.getControlChar(Attributes.ControlChar.VKILL);
+ c_cc[VREPRINT] = (byte) t.getControlChar(Attributes.ControlChar.VREPRINT);
+ c_cc[VINTR] = (byte) t.getControlChar(Attributes.ControlChar.VINTR);
+ c_cc[VQUIT] = (byte) t.getControlChar(Attributes.ControlChar.VQUIT);
+ c_cc[VSUSP] = (byte) t.getControlChar(Attributes.ControlChar.VSUSP);
+ c_cc[VDSUSP] = (byte) t.getControlChar(Attributes.ControlChar.VDSUSP);
+ c_cc[VSTART] = (byte) t.getControlChar(Attributes.ControlChar.VSTART);
+ c_cc[VSTOP] = (byte) t.getControlChar(Attributes.ControlChar.VSTOP);
+ c_cc[VLNEXT] = (byte) t.getControlChar(Attributes.ControlChar.VLNEXT);
+ c_cc[VDISCARD] = (byte) t.getControlChar(Attributes.ControlChar.VDISCARD);
+ c_cc[VMIN] = (byte) t.getControlChar(Attributes.ControlChar.VMIN);
+ c_cc[VTIME] = (byte) t.getControlChar(Attributes.ControlChar.VTIME);
+ c_cc[VSTATUS] = (byte) t.getControlChar(Attributes.ControlChar.VSTATUS);
+ c_cc().copyFrom(java.lang.foreign.MemorySegment.ofArray(c_cc));
+ }
+
+ java.lang.foreign.MemorySegment segment() {
+ return seg;
+ }
+
+ long c_iflag() {
+ return (long) c_iflag.get(seg);
+ }
+
+ void c_iflag(long f) {
+ c_iflag.set(seg, f);
+ }
+
+ long c_oflag() {
+ return (long) c_oflag.get(seg);
+ }
+
+ void c_oflag(long f) {
+ c_oflag.set(seg, f);
+ }
+
+ long c_cflag() {
+ return (long) c_cflag.get(seg);
+ }
+
+ void c_cflag(long f) {
+ c_cflag.set(seg, f);
+ }
+
+ long c_lflag() {
+ return (long) c_lflag.get(seg);
+ }
+
+ void c_lflag(long f) {
+ c_lflag.set(seg, f);
+ }
+
+ java.lang.foreign.MemorySegment c_cc() {
+ return seg.asSlice(32, 20);
+ }
+
+ long c_ispeed() {
+ return (long) c_ispeed.get(seg);
+ }
+
+ void c_ispeed(long f) {
+ c_ispeed.set(seg, f);
+ }
+
+ long c_ospeed() {
+ return (long) c_ospeed.get(seg);
+ }
+
+ void c_ospeed(long f) {
+ c_ospeed.set(seg, f);
+ }
+
+ private static long setFlag(boolean flag, long value, long org) {
+ return flag ? org | value : org;
+ }
+
+ private static > void addFlag(long value, EnumSet flags, T flag, int v) {
+ if ((value & v) != 0) {
+ flags.add(flag);
+ }
+ }
+
+ public Attributes asAttributes() {
+ Attributes attr = new Attributes();
+ // Input flags
+ long c_iflag = c_iflag();
+ EnumSet iflag = attr.getInputFlags();
+ addFlag(c_iflag, iflag, Attributes.InputFlag.IGNBRK, IGNBRK);
+ addFlag(c_iflag, iflag, Attributes.InputFlag.IGNBRK, IGNBRK);
+ addFlag(c_iflag, iflag, Attributes.InputFlag.BRKINT, BRKINT);
+ addFlag(c_iflag, iflag, Attributes.InputFlag.IGNPAR, IGNPAR);
+ addFlag(c_iflag, iflag, Attributes.InputFlag.PARMRK, PARMRK);
+ addFlag(c_iflag, iflag, Attributes.InputFlag.INPCK, INPCK);
+ addFlag(c_iflag, iflag, Attributes.InputFlag.ISTRIP, ISTRIP);
+ addFlag(c_iflag, iflag, Attributes.InputFlag.INLCR, INLCR);
+ addFlag(c_iflag, iflag, Attributes.InputFlag.IGNCR, IGNCR);
+ addFlag(c_iflag, iflag, Attributes.InputFlag.ICRNL, ICRNL);
+ addFlag(c_iflag, iflag, Attributes.InputFlag.IXON, IXON);
+ addFlag(c_iflag, iflag, Attributes.InputFlag.IXOFF, IXOFF);
+ addFlag(c_iflag, iflag, Attributes.InputFlag.IXANY, IXANY);
+ addFlag(c_iflag, iflag, Attributes.InputFlag.IMAXBEL, IMAXBEL);
+ addFlag(c_iflag, iflag, Attributes.InputFlag.IUTF8, IUTF8);
+ // Output flags
+ long c_oflag = c_oflag();
+ EnumSet oflag = attr.getOutputFlags();
+ addFlag(c_oflag, oflag, Attributes.OutputFlag.OPOST, OPOST);
+ addFlag(c_oflag, oflag, Attributes.OutputFlag.ONLCR, ONLCR);
+ addFlag(c_oflag, oflag, Attributes.OutputFlag.OXTABS, OXTABS);
+ addFlag(c_oflag, oflag, Attributes.OutputFlag.ONOEOT, ONOEOT);
+ addFlag(c_oflag, oflag, Attributes.OutputFlag.OCRNL, OCRNL);
+ addFlag(c_oflag, oflag, Attributes.OutputFlag.ONOCR, ONOCR);
+ addFlag(c_oflag, oflag, Attributes.OutputFlag.ONLRET, ONLRET);
+ addFlag(c_oflag, oflag, Attributes.OutputFlag.OFILL, OFILL);
+ addFlag(c_oflag, oflag, Attributes.OutputFlag.NLDLY, NLDLY);
+ addFlag(c_oflag, oflag, Attributes.OutputFlag.TABDLY, TABDLY);
+ addFlag(c_oflag, oflag, Attributes.OutputFlag.CRDLY, CRDLY);
+ addFlag(c_oflag, oflag, Attributes.OutputFlag.FFDLY, FFDLY);
+ addFlag(c_oflag, oflag, Attributes.OutputFlag.BSDLY, BSDLY);
+ addFlag(c_oflag, oflag, Attributes.OutputFlag.VTDLY, VTDLY);
+ addFlag(c_oflag, oflag, Attributes.OutputFlag.OFDEL, OFDEL);
+ // Control flags
+ long c_cflag = c_cflag();
+ EnumSet cflag = attr.getControlFlags();
+ addFlag(c_cflag, cflag, Attributes.ControlFlag.CIGNORE, CIGNORE);
+ addFlag(c_cflag, cflag, Attributes.ControlFlag.CS5, CS5);
+ addFlag(c_cflag, cflag, Attributes.ControlFlag.CS6, CS6);
+ addFlag(c_cflag, cflag, Attributes.ControlFlag.CS7, CS7);
+ addFlag(c_cflag, cflag, Attributes.ControlFlag.CS8, CS8);
+ addFlag(c_cflag, cflag, Attributes.ControlFlag.CSTOPB, CSTOPB);
+ addFlag(c_cflag, cflag, Attributes.ControlFlag.CREAD, CREAD);
+ addFlag(c_cflag, cflag, Attributes.ControlFlag.PARENB, PARENB);
+ addFlag(c_cflag, cflag, Attributes.ControlFlag.PARODD, PARODD);
+ addFlag(c_cflag, cflag, Attributes.ControlFlag.HUPCL, HUPCL);
+ addFlag(c_cflag, cflag, Attributes.ControlFlag.CLOCAL, CLOCAL);
+ addFlag(c_cflag, cflag, Attributes.ControlFlag.CCTS_OFLOW, CCTS_OFLOW);
+ addFlag(c_cflag, cflag, Attributes.ControlFlag.CRTS_IFLOW, CRTS_IFLOW);
+ addFlag(c_cflag, cflag, Attributes.ControlFlag.CDSR_OFLOW, CDSR_OFLOW);
+ addFlag(c_cflag, cflag, Attributes.ControlFlag.CCAR_OFLOW, CCAR_OFLOW);
+ // Local flags
+ long c_lflag = c_lflag();
+ EnumSet lflag = attr.getLocalFlags();
+ addFlag(c_lflag, lflag, Attributes.LocalFlag.ECHOKE, ECHOKE);
+ addFlag(c_lflag, lflag, Attributes.LocalFlag.ECHOE, ECHOE);
+ addFlag(c_lflag, lflag, Attributes.LocalFlag.ECHOK, ECHOK);
+ addFlag(c_lflag, lflag, Attributes.LocalFlag.ECHO, ECHO);
+ addFlag(c_lflag, lflag, Attributes.LocalFlag.ECHONL, ECHONL);
+ addFlag(c_lflag, lflag, Attributes.LocalFlag.ECHOPRT, ECHOPRT);
+ addFlag(c_lflag, lflag, Attributes.LocalFlag.ECHOCTL, ECHOCTL);
+ addFlag(c_lflag, lflag, Attributes.LocalFlag.ISIG, ISIG);
+ addFlag(c_lflag, lflag, Attributes.LocalFlag.ICANON, ICANON);
+ addFlag(c_lflag, lflag, Attributes.LocalFlag.ALTWERASE, ALTWERASE);
+ addFlag(c_lflag, lflag, Attributes.LocalFlag.IEXTEN, IEXTEN);
+ addFlag(c_lflag, lflag, Attributes.LocalFlag.EXTPROC, EXTPROC);
+ addFlag(c_lflag, lflag, Attributes.LocalFlag.TOSTOP, TOSTOP);
+ addFlag(c_lflag, lflag, Attributes.LocalFlag.FLUSHO, FLUSHO);
+ addFlag(c_lflag, lflag, Attributes.LocalFlag.NOKERNINFO, NOKERNINFO);
+ addFlag(c_lflag, lflag, Attributes.LocalFlag.PENDIN, PENDIN);
+ addFlag(c_lflag, lflag, Attributes.LocalFlag.NOFLSH, NOFLSH);
+ // Control chars
+ byte[] c_cc = c_cc().toArray(ValueLayout.JAVA_BYTE);
+ EnumMap cc = attr.getControlChars();
+ cc.put(Attributes.ControlChar.VEOF, (int) c_cc[VEOF]);
+ cc.put(Attributes.ControlChar.VEOL, (int) c_cc[VEOL]);
+ cc.put(Attributes.ControlChar.VEOL2, (int) c_cc[VEOL2]);
+ cc.put(Attributes.ControlChar.VERASE, (int) c_cc[VERASE]);
+ cc.put(Attributes.ControlChar.VWERASE, (int) c_cc[VWERASE]);
+ cc.put(Attributes.ControlChar.VKILL, (int) c_cc[VKILL]);
+ cc.put(Attributes.ControlChar.VREPRINT, (int) c_cc[VREPRINT]);
+ cc.put(Attributes.ControlChar.VINTR, (int) c_cc[VINTR]);
+ cc.put(Attributes.ControlChar.VQUIT, (int) c_cc[VQUIT]);
+ cc.put(Attributes.ControlChar.VSUSP, (int) c_cc[VSUSP]);
+ cc.put(Attributes.ControlChar.VDSUSP, (int) c_cc[VDSUSP]);
+ cc.put(Attributes.ControlChar.VSTART, (int) c_cc[VSTART]);
+ cc.put(Attributes.ControlChar.VSTOP, (int) c_cc[VSTOP]);
+ cc.put(Attributes.ControlChar.VLNEXT, (int) c_cc[VLNEXT]);
+ cc.put(Attributes.ControlChar.VDISCARD, (int) c_cc[VDISCARD]);
+ cc.put(Attributes.ControlChar.VMIN, (int) c_cc[VMIN]);
+ cc.put(Attributes.ControlChar.VTIME, (int) c_cc[VTIME]);
+ cc.put(Attributes.ControlChar.VSTATUS, (int) c_cc[VSTATUS]);
+ // Return
+ return attr;
+ }
+ }
+
+ static MethodHandle ioctl;
+ static MethodHandle isatty;
+ static MethodHandle openpty;
+ static MethodHandle tcsetattr;
+ static MethodHandle tcgetattr;
+ static MethodHandle ttyname_r;
+
+ static {
+ // methods
+ Linker linker = Linker.nativeLinker();
+ // https://man7.org/linux/man-pages/man2/ioctl.2.html
+ ioctl = linker.downcallHandle(
+ linker.defaultLookup().find("ioctl").get(),
+ FunctionDescriptor.of(
+ ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, ValueLayout.JAVA_LONG, ValueLayout.ADDRESS),
+ Linker.Option.firstVariadicArg(2));
+ // https://www.man7.org/linux/man-pages/man3/isatty.3.html
+ isatty = linker.downcallHandle(
+ linker.defaultLookup().find("isatty").get(),
+ FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT));
+ // https://man7.org/linux/man-pages/man3/openpty.3.html
+ openpty = linker.downcallHandle(
+ linker.defaultLookup().find("openpty").get(),
+ FunctionDescriptor.of(
+ ValueLayout.JAVA_INT,
+ ValueLayout.ADDRESS,
+ ValueLayout.ADDRESS,
+ ValueLayout.ADDRESS,
+ ValueLayout.ADDRESS,
+ ValueLayout.ADDRESS));
+ // https://man7.org/linux/man-pages/man3/tcsetattr.3p.html
+ tcsetattr = linker.downcallHandle(
+ linker.defaultLookup().find("tcsetattr").get(),
+ FunctionDescriptor.of(
+ ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, ValueLayout.ADDRESS));
+ // https://man7.org/linux/man-pages/man3/tcgetattr.3p.html
+ tcgetattr = linker.downcallHandle(
+ linker.defaultLookup().find("tcgetattr").get(),
+ FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, ValueLayout.ADDRESS));
+ // https://man7.org/linux/man-pages/man3/ttyname.3.html
+ ttyname_r = linker.downcallHandle(
+ linker.defaultLookup().find("ttyname_r").get(),
+ FunctionDescriptor.of(
+ ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG));
+ }
+
+ static Size getTerminalSize(int fd) {
+ try {
+ winsize ws = new winsize();
+ int res = (int) ioctl.invoke(fd, (long) TIOCGWINSZ, ws.segment());
+ return new Size(ws.ws_col(), ws.ws_row());
+ } catch (Throwable e) {
+ throw new RuntimeException("Unable to call ioctl(TIOCGWINSZ)", e);
+ }
+ }
+
+ static void setTerminalSize(int fd, Size size) {
+ try {
+ winsize ws = new winsize();
+ ws.ws_row((short) size.getRows());
+ ws.ws_col((short) size.getColumns());
+ int res = (int) ioctl.invoke(fd, TIOCSWINSZ, ws.segment());
+ } catch (Throwable e) {
+ throw new RuntimeException("Unable to call ioctl(TIOCSWINSZ)", e);
+ }
+ }
+
+ static Attributes getAttributes(int fd) {
+ try {
+ termios t = new termios();
+ int res = (int) tcgetattr.invoke(fd, t.segment());
+ return t.asAttributes();
+ } catch (Throwable e) {
+ throw new RuntimeException("Unable to call tcgetattr()", e);
+ }
+ }
+
+ static void setAttributes(int fd, Attributes attr) {
+ try {
+ termios t = new termios(attr);
+ int res = (int) tcsetattr.invoke(fd, TCSANOW, t.segment());
+ } catch (Throwable e) {
+ throw new RuntimeException("Unable to call tcsetattr()", e);
+ }
+ }
+
+ static boolean isTty(int fd) {
+ try {
+ return (int) isatty.invoke(fd) == 1;
+ } catch (Throwable e) {
+ throw new RuntimeException("Unable to call isatty()", e);
+ }
+ }
+
+ static String ttyName(int fd) {
+ try {
+ java.lang.foreign.MemorySegment buf =
+ java.lang.foreign.Arena.ofAuto().allocate(64);
+ int res = (int) ttyname_r.invoke(fd, buf, buf.byteSize());
+ byte[] data = buf.toArray(ValueLayout.JAVA_BYTE);
+ int len = 0;
+ while (data[len] != 0) {
+ len++;
+ }
+ return new String(data, 0, len);
+ } catch (Throwable e) {
+ throw new RuntimeException("Unable to call ttyname_r()", e);
+ }
+ }
+
+ static Pty openpty(Attributes attr, Size size) {
+ try {
+ java.lang.foreign.MemorySegment buf =
+ java.lang.foreign.Arena.ofAuto().allocate(64);
+ java.lang.foreign.MemorySegment master =
+ java.lang.foreign.Arena.ofAuto().allocate(ValueLayout.JAVA_INT);
+ java.lang.foreign.MemorySegment slave =
+ java.lang.foreign.Arena.ofAuto().allocate(ValueLayout.JAVA_INT);
+ int res = (int) openpty.invoke(
+ master,
+ slave,
+ buf,
+ attr != null ? new termios(attr).segment() : java.lang.foreign.MemorySegment.NULL,
+ size != null
+ ? new winsize((short) size.getRows(), (short) size.getColumns()).segment()
+ : java.lang.foreign.MemorySegment.NULL);
+ byte[] str = buf.toArray(ValueLayout.JAVA_BYTE);
+ int len = 0;
+ while (str[len] != 0) {
+ len++;
+ }
+ String device = new String(str, 0, len);
+ return new FfmNativePty(master.get(ValueLayout.JAVA_INT, 0), slave.get(ValueLayout.JAVA_INT, 0), device);
+ } catch (Throwable e) {
+ throw new RuntimeException("Unable to call openpty()", e);
+ }
+ }
+
+ // CONSTANTS
+
+ private static final int TIOCGWINSZ;
+ private static final int TIOCSWINSZ;
+
+ private static final int TCSANOW;
+ private static int TCSADRAIN;
+ private static int TCSAFLUSH;
+
+ private static final int VEOF;
+ private static final int VEOL;
+ private static final int VEOL2;
+ private static final int VERASE;
+ private static final int VWERASE;
+ private static final int VKILL;
+ private static final int VREPRINT;
+ private static int VERASE2;
+ private static final int VINTR;
+ private static final int VQUIT;
+ private static final int VSUSP;
+ private static int VDSUSP;
+ private static final int VSTART;
+ private static final int VSTOP;
+ private static final int VLNEXT;
+ private static final int VDISCARD;
+ private static final int VMIN;
+ private static int VSWTC;
+ private static final int VTIME;
+ private static int VSTATUS;
+
+ private static final int IGNBRK;
+ private static final int BRKINT;
+ private static final int IGNPAR;
+ private static final int PARMRK;
+ private static final int INPCK;
+ private static final int ISTRIP;
+ private static final int INLCR;
+ private static final int IGNCR;
+ private static final int ICRNL;
+ private static int IUCLC;
+ private static final int IXON;
+ private static final int IXOFF;
+ private static final int IXANY;
+ private static final int IMAXBEL;
+ private static int IUTF8;
+
+ private static final int OPOST;
+ private static int OLCUC;
+ private static final int ONLCR;
+ private static int OXTABS;
+ private static int NLDLY;
+ private static int NL0;
+ private static int NL1;
+ private static final int TABDLY;
+ private static int TAB0;
+ private static int TAB1;
+ private static int TAB2;
+ private static int TAB3;
+ private static int CRDLY;
+ private static int CR0;
+ private static int CR1;
+ private static int CR2;
+ private static int CR3;
+ private static int FFDLY;
+ private static int FF0;
+ private static int FF1;
+ private static int XTABS;
+ private static int BSDLY;
+ private static int BS0;
+ private static int BS1;
+ private static int VTDLY;
+ private static int VT0;
+ private static int VT1;
+ private static int CBAUD;
+ private static int B0;
+ private static int B50;
+ private static int B75;
+ private static int B110;
+ private static int B134;
+ private static int B150;
+ private static int B200;
+ private static int B300;
+ private static int B600;
+ private static int B1200;
+ private static int B1800;
+ private static int B2400;
+ private static int B4800;
+ private static int B9600;
+ private static int B19200;
+ private static int B38400;
+ private static int EXTA;
+ private static int EXTB;
+ private static int OFDEL;
+ private static int ONOEOT;
+ private static final int OCRNL;
+ private static int ONOCR;
+ private static final int ONLRET;
+ private static int OFILL;
+
+ private static int CIGNORE;
+ private static int CSIZE;
+ private static final int CS5;
+ private static final int CS6;
+ private static final int CS7;
+ private static final int CS8;
+ private static final int CSTOPB;
+ private static final int CREAD;
+ private static final int PARENB;
+ private static final int PARODD;
+ private static final int HUPCL;
+ private static final int CLOCAL;
+ private static int CCTS_OFLOW;
+ private static int CRTS_IFLOW;
+ private static int CDTR_IFLOW;
+ private static int CDSR_OFLOW;
+ private static int CCAR_OFLOW;
+
+ private static final int ECHOKE;
+ private static final int ECHOE;
+ private static final int ECHOK;
+ private static final int ECHO;
+ private static final int ECHONL;
+ private static final int ECHOPRT;
+ private static final int ECHOCTL;
+ private static final int ISIG;
+ private static final int ICANON;
+ private static int XCASE;
+ private static int ALTWERASE;
+ private static final int IEXTEN;
+ private static final int EXTPROC;
+ private static final int TOSTOP;
+ private static final int FLUSHO;
+ private static int NOKERNINFO;
+ private static final int PENDIN;
+ private static final int NOFLSH;
+
+ static {
+ String osName = System.getProperty("os.name");
+ if (osName.startsWith("Linux")) {
+ String arch = System.getProperty("os.arch");
+ boolean isMipsPpcOrSparc = arch.equals("mips")
+ || arch.equals("mips64")
+ || arch.equals("mipsel")
+ || arch.equals("mips64el")
+ || arch.startsWith("ppc")
+ || arch.startsWith("sparc");
+ TIOCGWINSZ = isMipsPpcOrSparc ? 0x40087468 : 0x00005413;
+ TIOCSWINSZ = isMipsPpcOrSparc ? 0x80087467 : 0x00005414;
+
+ TCSANOW = 0x0;
+ TCSADRAIN = 0x1;
+ TCSAFLUSH = 0x2;
+
+ VINTR = 0;
+ VQUIT = 1;
+ VERASE = 2;
+ VKILL = 3;
+ VEOF = 4;
+ VTIME = 5;
+ VMIN = 6;
+ VSWTC = 7;
+ VSTART = 8;
+ VSTOP = 9;
+ VSUSP = 10;
+ VEOL = 11;
+ VREPRINT = 12;
+ VDISCARD = 13;
+ VWERASE = 14;
+ VLNEXT = 15;
+ VEOL2 = 16;
+
+ IGNBRK = 0x0000001;
+ BRKINT = 0x0000002;
+ IGNPAR = 0x0000004;
+ PARMRK = 0x0000008;
+ INPCK = 0x0000010;
+ ISTRIP = 0x0000020;
+ INLCR = 0x0000040;
+ IGNCR = 0x0000080;
+ ICRNL = 0x0000100;
+ IUCLC = 0x0000200;
+ IXON = 0x0000400;
+ IXANY = 0x0000800;
+ IXOFF = 0x0001000;
+ IMAXBEL = 0x0002000;
+ IUTF8 = 0x0004000;
+
+ OPOST = 0x0000001;
+ OLCUC = 0x0000002;
+ ONLCR = 0x0000004;
+ OCRNL = 0x0000008;
+ ONOCR = 0x0000010;
+ ONLRET = 0x0000020;
+ OFILL = 0x0000040;
+ OFDEL = 0x0000080;
+ NLDLY = 0x0000100;
+ NL0 = 0x0000000;
+ NL1 = 0x0000100;
+ CRDLY = 0x0000600;
+ CR0 = 0x0000000;
+ CR1 = 0x0000200;
+ CR2 = 0x0000400;
+ CR3 = 0x0000600;
+ TABDLY = 0x0001800;
+ TAB0 = 0x0000000;
+ TAB1 = 0x0000800;
+ TAB2 = 0x0001000;
+ TAB3 = 0x0001800;
+ XTABS = 0x0001800;
+ BSDLY = 0x0002000;
+ BS0 = 0x0000000;
+ BS1 = 0x0002000;
+ VTDLY = 0x0004000;
+ VT0 = 0x0000000;
+ VT1 = 0x0004000;
+ FFDLY = 0x0008000;
+ FF0 = 0x0000000;
+ FF1 = 0x0008000;
+
+ CBAUD = 0x000100f;
+ B0 = 0x0000000;
+ B50 = 0x0000001;
+ B75 = 0x0000002;
+ B110 = 0x0000003;
+ B134 = 0x0000004;
+ B150 = 0x0000005;
+ B200 = 0x0000006;
+ B300 = 0x0000007;
+ B600 = 0x0000008;
+ B1200 = 0x0000009;
+ B1800 = 0x000000a;
+ B2400 = 0x000000b;
+ B4800 = 0x000000c;
+ B9600 = 0x000000d;
+ B19200 = 0x000000e;
+ B38400 = 0x000000f;
+ EXTA = B19200;
+ EXTB = B38400;
+ CSIZE = 0x0000030;
+ CS5 = 0x0000000;
+ CS6 = 0x0000010;
+ CS7 = 0x0000020;
+ CS8 = 0x0000030;
+ CSTOPB = 0x0000040;
+ CREAD = 0x0000080;
+ PARENB = 0x0000100;
+ PARODD = 0x0000200;
+ HUPCL = 0x0000400;
+ CLOCAL = 0x0000800;
+
+ ISIG = 0x0000001;
+ ICANON = 0x0000002;
+ XCASE = 0x0000004;
+ ECHO = 0x0000008;
+ ECHOE = 0x0000010;
+ ECHOK = 0x0000020;
+ ECHONL = 0x0000040;
+ NOFLSH = 0x0000080;
+ TOSTOP = 0x0000100;
+ ECHOCTL = 0x0000200;
+ ECHOPRT = 0x0000400;
+ ECHOKE = 0x0000800;
+ FLUSHO = 0x0001000;
+ PENDIN = 0x0002000;
+ IEXTEN = 0x0008000;
+ EXTPROC = 0x0010000;
+ } else if (osName.startsWith("Solaris") || osName.startsWith("SunOS")) {
+ int _TIOC = ('T' << 8);
+ TIOCGWINSZ = (_TIOC | 104);
+ TIOCSWINSZ = (_TIOC | 103);
+
+ TCSANOW = 0x0;
+ TCSADRAIN = 0x1;
+ TCSAFLUSH = 0x2;
+
+ VINTR = 0;
+ VQUIT = 1;
+ VERASE = 2;
+ VKILL = 3;
+ VEOF = 4;
+ VTIME = 5;
+ VMIN = 6;
+ VSWTC = 7;
+ VSTART = 8;
+ VSTOP = 9;
+ VSUSP = 10;
+ VEOL = 11;
+ VREPRINT = 12;
+ VDISCARD = 13;
+ VWERASE = 14;
+ VLNEXT = 15;
+ VEOL2 = 16;
+
+ IGNBRK = 0x0000001;
+ BRKINT = 0x0000002;
+ IGNPAR = 0x0000004;
+ PARMRK = 0x0000010;
+ INPCK = 0x0000020;
+ ISTRIP = 0x0000040;
+ INLCR = 0x0000100;
+ IGNCR = 0x0000200;
+ ICRNL = 0x0000400;
+ IUCLC = 0x0001000;
+ IXON = 0x0002000;
+ IXANY = 0x0004000;
+ IXOFF = 0x0010000;
+ IMAXBEL = 0x0020000;
+ IUTF8 = 0x0040000;
+
+ OPOST = 0x0000001;
+ OLCUC = 0x0000002;
+ ONLCR = 0x0000004;
+ OCRNL = 0x0000010;
+ ONOCR = 0x0000020;
+ ONLRET = 0x0000040;
+ OFILL = 0x0000100;
+ OFDEL = 0x0000200;
+ NLDLY = 0x0000400;
+ NL0 = 0x0000000;
+ NL1 = 0x0000400;
+ CRDLY = 0x0003000;
+ CR0 = 0x0000000;
+ CR1 = 0x0001000;
+ CR2 = 0x0002000;
+ CR3 = 0x0003000;
+ TABDLY = 0x0014000;
+ TAB0 = 0x0000000;
+ TAB1 = 0x0004000;
+ TAB2 = 0x0010000;
+ TAB3 = 0x0014000;
+ XTABS = 0x0014000;
+ BSDLY = 0x0020000;
+ BS0 = 0x0000000;
+ BS1 = 0x0020000;
+ VTDLY = 0x0040000;
+ VT0 = 0x0000000;
+ VT1 = 0x0040000;
+ FFDLY = 0x0100000;
+ FF0 = 0x0000000;
+ FF1 = 0x0100000;
+
+ CBAUD = 0x0010017;
+ B0 = 0x0000000;
+ B50 = 0x0000001;
+ B75 = 0x0000002;
+ B110 = 0x0000003;
+ B134 = 0x0000004;
+ B150 = 0x0000005;
+ B200 = 0x0000006;
+ B300 = 0x0000007;
+ B600 = 0x0000010;
+ B1200 = 0x0000011;
+ B1800 = 0x0000012;
+ B2400 = 0x0000013;
+ B4800 = 0x0000014;
+ B9600 = 0x0000015;
+ B19200 = 0x0000016;
+ B38400 = 0x0000017;
+ EXTA = 0xB19200;
+ EXTB = 0xB38400;
+ CSIZE = 0x0000060;
+ CS5 = 0x0000000;
+ CS6 = 0x0000020;
+ CS7 = 0x0000040;
+ CS8 = 0x0000060;
+ CSTOPB = 0x0000100;
+ CREAD = 0x0000200;
+ PARENB = 0x0000400;
+ PARODD = 0x0001000;
+ HUPCL = 0x0002000;
+ CLOCAL = 0x0004000;
+
+ ISIG = 0x0000001;
+ ICANON = 0x0000002;
+ XCASE = 0x0000004;
+ ECHO = 0x0000010;
+ ECHOE = 0x0000020;
+ ECHOK = 0x0000040;
+ ECHONL = 0x0000100;
+ NOFLSH = 0x0000200;
+ TOSTOP = 0x0000400;
+ ECHOCTL = 0x0001000;
+ ECHOPRT = 0x0002000;
+ ECHOKE = 0x0004000;
+ FLUSHO = 0x0010000;
+ PENDIN = 0x0040000;
+ IEXTEN = 0x0100000;
+ EXTPROC = 0x0200000;
+ } else if (osName.startsWith("Mac") || osName.startsWith("Darwin")) {
+ TIOCGWINSZ = 0x40087468;
+ TIOCSWINSZ = 0x80087467;
+
+ TCSANOW = 0x00000000;
+
+ VEOF = 0;
+ VEOL = 1;
+ VEOL2 = 2;
+ VERASE = 3;
+ VWERASE = 4;
+ VKILL = 5;
+ VREPRINT = 6;
+ VINTR = 8;
+ VQUIT = 9;
+ VSUSP = 10;
+ VDSUSP = 11;
+ VSTART = 12;
+ VSTOP = 13;
+ VLNEXT = 14;
+ VDISCARD = 15;
+ VMIN = 16;
+ VTIME = 17;
+ VSTATUS = 18;
+
+ IGNBRK = 0x00000001;
+ BRKINT = 0x00000002;
+ IGNPAR = 0x00000004;
+ PARMRK = 0x00000008;
+ INPCK = 0x00000010;
+ ISTRIP = 0x00000020;
+ INLCR = 0x00000040;
+ IGNCR = 0x00000080;
+ ICRNL = 0x00000100;
+ IXON = 0x00000200;
+ IXOFF = 0x00000400;
+ IXANY = 0x00000800;
+ IMAXBEL = 0x00002000;
+ IUTF8 = 0x00004000;
+
+ OPOST = 0x00000001;
+ ONLCR = 0x00000002;
+ OXTABS = 0x00000004;
+ ONOEOT = 0x00000008;
+ OCRNL = 0x00000010;
+ ONOCR = 0x00000020;
+ ONLRET = 0x00000040;
+ OFILL = 0x00000080;
+ NLDLY = 0x00000300;
+ TABDLY = 0x00000c04;
+ CRDLY = 0x00003000;
+ FFDLY = 0x00004000;
+ BSDLY = 0x00008000;
+ VTDLY = 0x00010000;
+ OFDEL = 0x00020000;
+
+ CIGNORE = 0x00000001;
+ CS5 = 0x00000000;
+ CS6 = 0x00000100;
+ CS7 = 0x00000200;
+ CS8 = 0x00000300;
+ CSTOPB = 0x00000400;
+ CREAD = 0x00000800;
+ PARENB = 0x00001000;
+ PARODD = 0x00002000;
+ HUPCL = 0x00004000;
+ CLOCAL = 0x00008000;
+ CCTS_OFLOW = 0x00010000;
+ CRTS_IFLOW = 0x00020000;
+ CDTR_IFLOW = 0x00040000;
+ CDSR_OFLOW = 0x00080000;
+ CCAR_OFLOW = 0x00100000;
+
+ ECHOKE = 0x00000001;
+ ECHOE = 0x00000002;
+ ECHOK = 0x00000004;
+ ECHO = 0x00000008;
+ ECHONL = 0x00000010;
+ ECHOPRT = 0x00000020;
+ ECHOCTL = 0x00000040;
+ ISIG = 0x00000080;
+ ICANON = 0x00000100;
+ ALTWERASE = 0x00000200;
+ IEXTEN = 0x00000400;
+ EXTPROC = 0x00000800;
+ TOSTOP = 0x00400000;
+ FLUSHO = 0x00800000;
+ NOKERNINFO = 0x02000000;
+ PENDIN = 0x20000000;
+ NOFLSH = 0x80000000;
+ } else if (osName.startsWith("FreeBSD")) {
+ TIOCGWINSZ = 0x40087468;
+ TIOCSWINSZ = 0x80087467;
+
+ TCSANOW = 0x0;
+ TCSADRAIN = 0x1;
+ TCSAFLUSH = 0x2;
+
+ VEOF = 0;
+ VEOL = 1;
+ VEOL2 = 2;
+ VERASE = 3;
+ VWERASE = 4;
+ VKILL = 5;
+ VREPRINT = 6;
+ VERASE2 = 7;
+ VINTR = 8;
+ VQUIT = 9;
+ VSUSP = 10;
+ VDSUSP = 11;
+ VSTART = 12;
+ VSTOP = 13;
+ VLNEXT = 14;
+ VDISCARD = 15;
+ VMIN = 16;
+ VTIME = 17;
+ VSTATUS = 18;
+
+ IGNBRK = 0x0000001;
+ BRKINT = 0x0000002;
+ IGNPAR = 0x0000004;
+ PARMRK = 0x0000008;
+ INPCK = 0x0000010;
+ ISTRIP = 0x0000020;
+ INLCR = 0x0000040;
+ IGNCR = 0x0000080;
+ ICRNL = 0x0000100;
+ IXON = 0x0000200;
+ IXOFF = 0x0000400;
+ IXANY = 0x0000800;
+ IMAXBEL = 0x0002000;
+
+ OPOST = 0x0000001;
+ ONLCR = 0x0000002;
+ TABDLY = 0x0000004;
+ TAB0 = 0x0000000;
+ TAB3 = 0x0000004;
+ ONOEOT = 0x0000008;
+ OCRNL = 0x0000010;
+ ONLRET = 0x0000040;
+
+ CIGNORE = 0x0000001;
+ CSIZE = 0x0000300;
+ CS5 = 0x0000000;
+ CS6 = 0x0000100;
+ CS7 = 0x0000200;
+ CS8 = 0x0000300;
+ CSTOPB = 0x0000400;
+ CREAD = 0x0000800;
+ PARENB = 0x0001000;
+ PARODD = 0x0002000;
+ HUPCL = 0x0004000;
+ CLOCAL = 0x0008000;
+
+ ECHOKE = 0x0000001;
+ ECHOE = 0x0000002;
+ ECHOK = 0x0000004;
+ ECHO = 0x0000008;
+ ECHONL = 0x0000010;
+ ECHOPRT = 0x0000020;
+ ECHOCTL = 0x0000040;
+ ISIG = 0x0000080;
+ ICANON = 0x0000100;
+ ALTWERASE = 0x000200;
+ IEXTEN = 0x0000400;
+ EXTPROC = 0x0000800;
+ TOSTOP = 0x0400000;
+ FLUSHO = 0x0800000;
+ PENDIN = 0x2000000;
+ NOFLSH = 0x8000000;
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/FfmNativePty.java b/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/FfmNativePty.java
new file mode 100644
index 000000000..5ede7ed1b
--- /dev/null
+++ b/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/FfmNativePty.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2022-2023, the original author(s).
+ *
+ * This software is distributable under the BSD license. See the terms of the
+ * BSD license in the documentation provided with this software.
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+package org.jline.terminal.impl.ffm;
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.jline.terminal.Attributes;
+import org.jline.terminal.Size;
+import org.jline.terminal.impl.AbstractPty;
+import org.jline.terminal.spi.TerminalProvider;
+
+class FfmNativePty extends AbstractPty {
+ private final int master;
+ private final int slave;
+ private final int slaveOut;
+ private final String name;
+ private final FileDescriptor masterFD;
+ private final FileDescriptor slaveFD;
+ private final FileDescriptor slaveOutFD;
+
+ public FfmNativePty(int master, int slave, String name) {
+ this(master, newDescriptor(master), slave, newDescriptor(slave), slave, newDescriptor(slave), name);
+ }
+
+ public FfmNativePty(
+ int master,
+ FileDescriptor masterFD,
+ int slave,
+ FileDescriptor slaveFD,
+ int slaveOut,
+ FileDescriptor slaveOutFD,
+ String name) {
+ this.master = master;
+ this.slave = slave;
+ this.slaveOut = slaveOut;
+ this.name = name;
+ this.masterFD = masterFD;
+ this.slaveFD = slaveFD;
+ this.slaveOutFD = slaveOutFD;
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (master > 0) {
+ getMasterInput().close();
+ }
+ if (slave > 0) {
+ getSlaveInput().close();
+ }
+ }
+
+ public int getMaster() {
+ return master;
+ }
+
+ public int getSlave() {
+ return slave;
+ }
+
+ public int getSlaveOut() {
+ return slaveOut;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public FileDescriptor getMasterFD() {
+ return masterFD;
+ }
+
+ public FileDescriptor getSlaveFD() {
+ return slaveFD;
+ }
+
+ public FileDescriptor getSlaveOutFD() {
+ return slaveOutFD;
+ }
+
+ public InputStream getMasterInput() {
+ return new FileInputStream(getMasterFD());
+ }
+
+ public OutputStream getMasterOutput() {
+ return new FileOutputStream(getMasterFD());
+ }
+
+ protected InputStream doGetSlaveInput() {
+ return new FileInputStream(getSlaveFD());
+ }
+
+ public OutputStream getSlaveOutput() {
+ return new FileOutputStream(getSlaveOutFD());
+ }
+
+ @Override
+ public Attributes getAttr() throws IOException {
+ return CLibrary.getAttributes(slave);
+ }
+
+ @Override
+ protected void doSetAttr(Attributes attr) throws IOException {
+ CLibrary.setAttributes(slave, attr);
+ }
+
+ @Override
+ public Size getSize() throws IOException {
+ return CLibrary.getTerminalSize(slave);
+ }
+
+ @Override
+ public void setSize(Size size) throws IOException {
+ CLibrary.setTerminalSize(slave, size);
+ }
+
+ @Override
+ public String toString() {
+ return "FfmNativePty[" + getName() + "]";
+ }
+
+ public static boolean isPosixSystemStream(TerminalProvider.Stream stream) {
+ switch (stream) {
+ case Input:
+ return CLibrary.isTty(0);
+ case Output:
+ return CLibrary.isTty(1);
+ case Error:
+ return CLibrary.isTty(2);
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ public static String posixSystemStreamName(TerminalProvider.Stream stream) {
+ switch (stream) {
+ case Input:
+ return CLibrary.ttyName(0);
+ case Output:
+ return CLibrary.ttyName(1);
+ case Error:
+ return CLibrary.ttyName(2);
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+}
diff --git a/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/FfmTerminalProvider.java b/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/FfmTerminalProvider.java
new file mode 100644
index 000000000..a0e94a9d3
--- /dev/null
+++ b/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/FfmTerminalProvider.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2022-2023, the original author(s).
+ *
+ * This software is distributable under the BSD license. See the terms of the
+ * BSD license in the documentation provided with this software.
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+package org.jline.terminal.impl.ffm;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+
+import org.jline.terminal.Attributes;
+import org.jline.terminal.Size;
+import org.jline.terminal.Terminal;
+import org.jline.terminal.impl.PosixPtyTerminal;
+import org.jline.terminal.impl.PosixSysTerminal;
+import org.jline.terminal.spi.Pty;
+import org.jline.terminal.spi.TerminalProvider;
+import org.jline.utils.OSUtils;
+
+public class FfmTerminalProvider implements TerminalProvider {
+
+ public FfmTerminalProvider() {
+ if (!FfmTerminalProvider.class.getModule().isNativeAccessEnabled()) {
+ throw new UnsupportedOperationException(
+ "Native access is not enabled for the current module: " + FfmTerminalProvider.class.getModule());
+ }
+ }
+
+ @Override
+ public String name() {
+ return "ffm";
+ }
+
+ @Override
+ public Terminal sysTerminal(
+ String name,
+ String type,
+ boolean ansiPassThrough,
+ Charset encoding,
+ boolean nativeSignals,
+ Terminal.SignalHandler signalHandler,
+ boolean paused,
+ Stream consoleStream)
+ throws IOException {
+ if (OSUtils.IS_WINDOWS) {
+ return NativeWinSysTerminal.createTerminal(
+ name, type, ansiPassThrough, encoding, nativeSignals, signalHandler, paused, consoleStream);
+ } else {
+ Pty pty = new FfmNativePty(
+ -1,
+ null,
+ 0,
+ FileDescriptor.in,
+ consoleStream == Stream.Output ? 1 : 2,
+ consoleStream == Stream.Output ? FileDescriptor.out : FileDescriptor.err,
+ CLibrary.ttyName(0));
+ return new PosixSysTerminal(name, type, pty, encoding, nativeSignals, signalHandler);
+ }
+ }
+
+ @Override
+ public Terminal newTerminal(
+ String name,
+ String type,
+ InputStream in,
+ OutputStream out,
+ Charset encoding,
+ Terminal.SignalHandler signalHandler,
+ boolean paused,
+ Attributes attributes,
+ Size size)
+ throws IOException {
+ Pty pty = CLibrary.openpty(attributes, size);
+ return new PosixPtyTerminal(name, type, pty, in, out, encoding, signalHandler, paused);
+ }
+
+ @Override
+ public boolean isSystemStream(Stream stream) {
+ if (OSUtils.IS_WINDOWS) {
+ return isWindowsSystemStream(stream);
+ } else {
+ return isPosixSystemStream(stream);
+ }
+ }
+
+ public boolean isWindowsSystemStream(Stream stream) {
+ return NativeWinSysTerminal.isWindowsSystemStream(stream);
+ }
+
+ public boolean isPosixSystemStream(Stream stream) {
+ return FfmNativePty.isPosixSystemStream(stream);
+ }
+
+ @Override
+ public String systemStreamName(Stream stream) {
+ return FfmNativePty.posixSystemStreamName(stream);
+ }
+}
diff --git a/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/Kernel32.java b/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/Kernel32.java
new file mode 100644
index 000000000..1a2d313d7
--- /dev/null
+++ b/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/Kernel32.java
@@ -0,0 +1,925 @@
+/*
+ * Copyright (c) 2009-2023, the original author(s).
+ *
+ * This software is distributable under the BSD license. See the terms of the
+ * BSD license in the documentation provided with this software.
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+package org.jline.terminal.impl.ffm;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.VarHandle;
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
+
+@SuppressWarnings({"unused", "preview"})
+final class Kernel32 {
+
+ public static final int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
+
+ public static final int INVALID_HANDLE_VALUE = -1;
+ public static final int STD_INPUT_HANDLE = -10;
+ public static final int STD_OUTPUT_HANDLE = -11;
+ public static final int STD_ERROR_HANDLE = -12;
+
+ public static final int ENABLE_PROCESSED_INPUT = 0x0001;
+ public static final int ENABLE_LINE_INPUT = 0x0002;
+ public static final int ENABLE_ECHO_INPUT = 0x0004;
+ public static final int ENABLE_WINDOW_INPUT = 0x0008;
+ public static final int ENABLE_MOUSE_INPUT = 0x0010;
+ public static final int ENABLE_INSERT_MODE = 0x0020;
+ public static final int ENABLE_QUICK_EDIT_MODE = 0x0040;
+ public static final int ENABLE_EXTENDED_FLAGS = 0x0080;
+
+ public static final int RIGHT_ALT_PRESSED = 0x0001;
+ public static final int LEFT_ALT_PRESSED = 0x0002;
+ public static final int RIGHT_CTRL_PRESSED = 0x0004;
+ public static final int LEFT_CTRL_PRESSED = 0x0008;
+ public static final int SHIFT_PRESSED = 0x0010;
+
+ public static final int FOREGROUND_BLUE = 0x0001;
+ public static final int FOREGROUND_GREEN = 0x0002;
+ public static final int FOREGROUND_RED = 0x0004;
+ public static final int FOREGROUND_INTENSITY = 0x0008;
+ public static final int BACKGROUND_BLUE = 0x0010;
+ public static final int BACKGROUND_GREEN = 0x0020;
+ public static final int BACKGROUND_RED = 0x0040;
+ public static final int BACKGROUND_INTENSITY = 0x0080;
+
+ // Button state
+ public static final int FROM_LEFT_1ST_BUTTON_PRESSED = 0x0001;
+ public static final int RIGHTMOST_BUTTON_PRESSED = 0x0002;
+ public static final int FROM_LEFT_2ND_BUTTON_PRESSED = 0x0004;
+ public static final int FROM_LEFT_3RD_BUTTON_PRESSED = 0x0008;
+ public static final int FROM_LEFT_4TH_BUTTON_PRESSED = 0x0010;
+
+ // Event flags
+ public static final int MOUSE_MOVED = 0x0001;
+ public static final int DOUBLE_CLICK = 0x0002;
+ public static final int MOUSE_WHEELED = 0x0004;
+ public static final int MOUSE_HWHEELED = 0x0008;
+
+ // Event types
+ public static final short KEY_EVENT = 0x0001;
+ public static final short MOUSE_EVENT = 0x0002;
+ public static final short WINDOW_BUFFER_SIZE_EVENT = 0x0004;
+ public static final short MENU_EVENT = 0x0008;
+ public static final short FOCUS_EVENT = 0x0010;
+
+ public static int WaitForSingleObject(java.lang.foreign.MemorySegment hHandle, int dwMilliseconds) {
+ MethodHandle mh$ = requireNonNull(WaitForSingleObject$MH, "WaitForSingleObject");
+ try {
+ return (int) mh$.invokeExact(hHandle, dwMilliseconds);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+
+ public static java.lang.foreign.MemorySegment GetStdHandle(int nStdHandle) {
+ MethodHandle mh$ = requireNonNull(GetStdHandle$MH, "GetStdHandle");
+ try {
+ return (java.lang.foreign.MemorySegment) mh$.invokeExact(nStdHandle);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+
+ public static int FormatMessageW(
+ int dwFlags,
+ java.lang.foreign.MemorySegment lpSource,
+ int dwMessageId,
+ int dwLanguageId,
+ java.lang.foreign.MemorySegment lpBuffer,
+ int nSize,
+ java.lang.foreign.MemorySegment Arguments) {
+ MethodHandle mh$ = requireNonNull(FormatMessageW$MH, "FormatMessageW");
+ try {
+ return (int) mh$.invokeExact(dwFlags, lpSource, dwMessageId, dwLanguageId, lpBuffer, nSize, Arguments);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+
+ public static int SetConsoleTextAttribute(java.lang.foreign.MemorySegment hConsoleOutput, short wAttributes) {
+ MethodHandle mh$ = requireNonNull(SetConsoleTextAttribute$MH, "SetConsoleTextAttribute");
+ try {
+ return (int) mh$.invokeExact(hConsoleOutput, wAttributes);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+
+ public static int SetConsoleMode(java.lang.foreign.MemorySegment hConsoleHandle, int dwMode) {
+ MethodHandle mh$ = requireNonNull(SetConsoleMode$MH, "SetConsoleMode");
+ try {
+ return (int) mh$.invokeExact(hConsoleHandle, dwMode);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+
+ public static int GetConsoleMode(
+ java.lang.foreign.MemorySegment hConsoleHandle, java.lang.foreign.MemorySegment lpMode) {
+ MethodHandle mh$ = requireNonNull(GetConsoleMode$MH, "GetConsoleMode");
+ try {
+ return (int) mh$.invokeExact(hConsoleHandle, lpMode);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+
+ public static int SetConsoleTitleW(java.lang.foreign.MemorySegment lpConsoleTitle) {
+ MethodHandle mh$ = requireNonNull(SetConsoleTitleW$MH, "SetConsoleTitleW");
+ try {
+ return (int) mh$.invokeExact(lpConsoleTitle);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+
+ public static int SetConsoleCursorPosition(java.lang.foreign.MemorySegment hConsoleOutput, COORD dwCursorPosition) {
+ MethodHandle mh$ = requireNonNull(SetConsoleCursorPosition$MH, "SetConsoleCursorPosition");
+ try {
+ return (int) mh$.invokeExact(hConsoleOutput, dwCursorPosition.seg);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+
+ public static int FillConsoleOutputCharacterW(
+ java.lang.foreign.MemorySegment hConsoleOutput,
+ char cCharacter,
+ int nLength,
+ COORD dwWriteCoord,
+ java.lang.foreign.MemorySegment lpNumberOfCharsWritten) {
+ MethodHandle mh$ = requireNonNull(FillConsoleOutputCharacterW$MH, "FillConsoleOutputCharacterW");
+ try {
+ return (int) mh$.invokeExact(hConsoleOutput, cCharacter, nLength, dwWriteCoord.seg, lpNumberOfCharsWritten);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+
+ public static int FillConsoleOutputAttribute(
+ java.lang.foreign.MemorySegment hConsoleOutput,
+ short wAttribute,
+ int nLength,
+ COORD dwWriteCoord,
+ java.lang.foreign.MemorySegment lpNumberOfAttrsWritten) {
+ MethodHandle mh$ = requireNonNull(FillConsoleOutputAttribute$MH, "FillConsoleOutputAttribute");
+ try {
+ return (int) mh$.invokeExact(hConsoleOutput, wAttribute, nLength, dwWriteCoord.seg, lpNumberOfAttrsWritten);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+
+ public static int WriteConsoleW(
+ java.lang.foreign.MemorySegment hConsoleOutput,
+ java.lang.foreign.MemorySegment lpBuffer,
+ int nNumberOfCharsToWrite,
+ java.lang.foreign.MemorySegment lpNumberOfCharsWritten,
+ java.lang.foreign.MemorySegment lpReserved) {
+ MethodHandle mh$ = requireNonNull(WriteConsoleW$MH, "WriteConsoleW");
+ try {
+ return (int) mh$.invokeExact(
+ hConsoleOutput, lpBuffer, nNumberOfCharsToWrite, lpNumberOfCharsWritten, lpReserved);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+
+ public static int ReadConsoleInputW(
+ java.lang.foreign.MemorySegment hConsoleInput,
+ java.lang.foreign.MemorySegment lpBuffer,
+ int nLength,
+ java.lang.foreign.MemorySegment lpNumberOfEventsRead) {
+ MethodHandle mh$ = requireNonNull(ReadConsoleInputW$MH, "ReadConsoleInputW");
+ try {
+ return (int) mh$.invokeExact(hConsoleInput, lpBuffer, nLength, lpNumberOfEventsRead);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+
+ public static int PeekConsoleInputW(
+ java.lang.foreign.MemorySegment hConsoleInput,
+ java.lang.foreign.MemorySegment lpBuffer,
+ int nLength,
+ java.lang.foreign.MemorySegment lpNumberOfEventsRead) {
+ MethodHandle mh$ = requireNonNull(PeekConsoleInputW$MH, "PeekConsoleInputW");
+ try {
+ return (int) mh$.invokeExact(hConsoleInput, lpBuffer, nLength, lpNumberOfEventsRead);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+
+ public static int GetConsoleScreenBufferInfo(
+ java.lang.foreign.MemorySegment hConsoleOutput, CONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo) {
+ MethodHandle mh$ = requireNonNull(GetConsoleScreenBufferInfo$MH, "GetConsoleScreenBufferInfo");
+ try {
+ return (int) mh$.invokeExact(hConsoleOutput, lpConsoleScreenBufferInfo.seg);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+
+ public static int ScrollConsoleScreenBuffer(
+ java.lang.foreign.MemorySegment hConsoleOutput,
+ SMALL_RECT lpScrollRectangle,
+ SMALL_RECT lpClipRectangle,
+ COORD dwDestinationOrigin,
+ CHAR_INFO lpFill) {
+ MethodHandle mh$ = requireNonNull(ScrollConsoleScreenBufferW$MH, "ScrollConsoleScreenBuffer");
+ try {
+ return (int)
+ mh$.invokeExact(hConsoleOutput, lpScrollRectangle, lpClipRectangle, dwDestinationOrigin, lpFill);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+
+ public static int GetLastError() {
+ MethodHandle mh$ = requireNonNull(GetLastError$MH, "GetLastError");
+ try {
+ return (int) mh$.invokeExact();
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+
+ public static int GetFileType(java.lang.foreign.MemorySegment hFile) {
+ MethodHandle mh$ = requireNonNull(GetFileType$MH, "GetFileType");
+ try {
+ return (int) mh$.invokeExact(hFile);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+
+ public static java.lang.foreign.MemorySegment _get_osfhandle(int fd) {
+ MethodHandle mh$ = requireNonNull(_get_osfhandle$MH, "_get_osfhandle");
+ try {
+ return (java.lang.foreign.MemorySegment) mh$.invokeExact(fd);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+
+ public static INPUT_RECORD[] readConsoleInputHelper(java.lang.foreign.MemorySegment handle, int count, boolean peek)
+ throws IOException {
+ return readConsoleInputHelper(java.lang.foreign.Arena.ofAuto(), handle, count, peek);
+ }
+
+ public static INPUT_RECORD[] readConsoleInputHelper(
+ java.lang.foreign.Arena arena, java.lang.foreign.MemorySegment handle, int count, boolean peek)
+ throws IOException {
+ java.lang.foreign.MemorySegment inputRecordPtr = arena.allocateArray(INPUT_RECORD.LAYOUT, count);
+ java.lang.foreign.MemorySegment length = arena.allocate(java.lang.foreign.ValueLayout.JAVA_INT, 0);
+ int res = peek
+ ? PeekConsoleInputW(handle, inputRecordPtr, count, length)
+ : ReadConsoleInputW(handle, inputRecordPtr, count, length);
+ if (res == 0) {
+ throw new IOException("ReadConsoleInputW failed: " + getLastErrorMessage());
+ }
+ int len = length.get(java.lang.foreign.ValueLayout.JAVA_INT, 0);
+ return inputRecordPtr
+ .elements(INPUT_RECORD.LAYOUT)
+ .map(INPUT_RECORD::new)
+ .limit(len)
+ .toArray(INPUT_RECORD[]::new);
+ }
+
+ public static String getLastErrorMessage() {
+ int errorCode = GetLastError();
+ return getErrorMessage(errorCode);
+ }
+
+ public static String getErrorMessage(int errorCode) {
+ int bufferSize = 160;
+ try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
+ java.lang.foreign.MemorySegment data = arena.allocate(bufferSize);
+ FormatMessageW(
+ FORMAT_MESSAGE_FROM_SYSTEM,
+ java.lang.foreign.MemorySegment.NULL,
+ errorCode,
+ 0,
+ data,
+ bufferSize,
+ java.lang.foreign.MemorySegment.NULL);
+ return new String(data.toArray(java.lang.foreign.ValueLayout.JAVA_BYTE), StandardCharsets.UTF_16LE).trim();
+ }
+ }
+
+ private static final java.lang.foreign.SymbolLookup SYMBOL_LOOKUP;
+
+ static {
+ System.loadLibrary("msvcrt");
+ System.loadLibrary("Kernel32");
+ SYMBOL_LOOKUP = java.lang.foreign.SymbolLookup.loaderLookup();
+ }
+
+ static MethodHandle downcallHandle(String name, java.lang.foreign.FunctionDescriptor fdesc) {
+ return SYMBOL_LOOKUP
+ .find(name)
+ .map(addr -> java.lang.foreign.Linker.nativeLinker().downcallHandle(addr, fdesc))
+ .orElse(null);
+ }
+
+ static final java.lang.foreign.ValueLayout.OfBoolean C_BOOL$LAYOUT = java.lang.foreign.ValueLayout.JAVA_BOOLEAN;
+ static final java.lang.foreign.ValueLayout.OfByte C_CHAR$LAYOUT = java.lang.foreign.ValueLayout.JAVA_BYTE;
+ static final java.lang.foreign.ValueLayout.OfChar C_WCHAR$LAYOUT = java.lang.foreign.ValueLayout.JAVA_CHAR;
+ static final java.lang.foreign.ValueLayout.OfShort C_SHORT$LAYOUT = java.lang.foreign.ValueLayout.JAVA_SHORT;
+ static final java.lang.foreign.ValueLayout.OfShort C_WORD$LAYOUT = java.lang.foreign.ValueLayout.JAVA_SHORT;
+ static final java.lang.foreign.ValueLayout.OfInt C_DWORD$LAYOUT = java.lang.foreign.ValueLayout.JAVA_INT;
+ static final java.lang.foreign.ValueLayout.OfInt C_INT$LAYOUT = java.lang.foreign.ValueLayout.JAVA_INT;
+ static final java.lang.foreign.ValueLayout.OfLong C_LONG$LAYOUT = java.lang.foreign.ValueLayout.JAVA_LONG;
+ static final java.lang.foreign.ValueLayout.OfLong C_LONG_LONG$LAYOUT = java.lang.foreign.ValueLayout.JAVA_LONG;
+ static final java.lang.foreign.ValueLayout.OfFloat C_FLOAT$LAYOUT = java.lang.foreign.ValueLayout.JAVA_FLOAT;
+ static final java.lang.foreign.ValueLayout.OfDouble C_DOUBLE$LAYOUT = java.lang.foreign.ValueLayout.JAVA_DOUBLE;
+ static final java.lang.foreign.AddressLayout C_POINTER$LAYOUT = java.lang.foreign.ValueLayout.ADDRESS;
+
+ static final MethodHandle WaitForSingleObject$MH = downcallHandle(
+ "WaitForSingleObject",
+ java.lang.foreign.FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, C_INT$LAYOUT));
+ static final MethodHandle GetStdHandle$MH =
+ downcallHandle("GetStdHandle", java.lang.foreign.FunctionDescriptor.of(C_POINTER$LAYOUT, C_INT$LAYOUT));
+ static final MethodHandle FormatMessageW$MH = downcallHandle(
+ "FormatMessageW",
+ java.lang.foreign.FunctionDescriptor.of(
+ C_INT$LAYOUT,
+ C_INT$LAYOUT,
+ C_POINTER$LAYOUT,
+ C_INT$LAYOUT,
+ C_INT$LAYOUT,
+ C_POINTER$LAYOUT,
+ C_INT$LAYOUT,
+ C_POINTER$LAYOUT));
+ static final MethodHandle SetConsoleTextAttribute$MH = downcallHandle(
+ "SetConsoleTextAttribute",
+ java.lang.foreign.FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, C_SHORT$LAYOUT));
+ static final MethodHandle SetConsoleMode$MH = downcallHandle(
+ "SetConsoleMode", java.lang.foreign.FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, C_INT$LAYOUT));
+ static final MethodHandle GetConsoleMode$MH = downcallHandle(
+ "GetConsoleMode",
+ java.lang.foreign.FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, C_POINTER$LAYOUT));
+
+ static final MethodHandle SetConsoleTitleW$MH =
+ downcallHandle("SetConsoleTitleW", java.lang.foreign.FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT));
+ static final MethodHandle SetConsoleCursorPosition$MH = downcallHandle(
+ "SetConsoleCursorPosition",
+ java.lang.foreign.FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, COORD.LAYOUT));
+ static final MethodHandle FillConsoleOutputCharacterW$MH = downcallHandle(
+ "FillConsoleOutputCharacterW",
+ java.lang.foreign.FunctionDescriptor.of(
+ C_INT$LAYOUT, C_POINTER$LAYOUT, C_WCHAR$LAYOUT, C_INT$LAYOUT, COORD.LAYOUT, C_POINTER$LAYOUT));
+ static final MethodHandle FillConsoleOutputAttribute$MH = downcallHandle(
+ "FillConsoleOutputAttribute",
+ java.lang.foreign.FunctionDescriptor.of(
+ C_INT$LAYOUT, C_POINTER$LAYOUT, C_SHORT$LAYOUT, C_INT$LAYOUT, COORD.LAYOUT, C_POINTER$LAYOUT));
+ static final MethodHandle WriteConsoleW$MH = downcallHandle(
+ "WriteConsoleW",
+ java.lang.foreign.FunctionDescriptor.of(
+ C_INT$LAYOUT,
+ C_POINTER$LAYOUT,
+ C_POINTER$LAYOUT,
+ C_INT$LAYOUT,
+ C_POINTER$LAYOUT,
+ C_POINTER$LAYOUT));
+
+ static final MethodHandle ReadConsoleInputW$MH = downcallHandle(
+ "ReadConsoleInputW",
+ java.lang.foreign.FunctionDescriptor.of(
+ C_INT$LAYOUT, C_POINTER$LAYOUT, C_POINTER$LAYOUT, C_INT$LAYOUT, C_POINTER$LAYOUT));
+ static final MethodHandle PeekConsoleInputW$MH = downcallHandle(
+ "PeekConsoleInputW",
+ java.lang.foreign.FunctionDescriptor.of(
+ C_INT$LAYOUT, C_POINTER$LAYOUT, C_POINTER$LAYOUT, C_INT$LAYOUT, C_POINTER$LAYOUT));
+
+ static final MethodHandle GetConsoleScreenBufferInfo$MH = downcallHandle(
+ "GetConsoleScreenBufferInfo",
+ java.lang.foreign.FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, C_POINTER$LAYOUT));
+
+ static final MethodHandle ScrollConsoleScreenBufferW$MH = downcallHandle(
+ "ScrollConsoleScreenBufferW",
+ java.lang.foreign.FunctionDescriptor.of(
+ C_INT$LAYOUT,
+ C_POINTER$LAYOUT,
+ C_POINTER$LAYOUT,
+ C_POINTER$LAYOUT,
+ COORD.LAYOUT,
+ C_POINTER$LAYOUT));
+ static final MethodHandle GetLastError$MH =
+ downcallHandle("GetLastError", java.lang.foreign.FunctionDescriptor.of(C_INT$LAYOUT));
+ static final MethodHandle GetFileType$MH =
+ downcallHandle("GetFileType", java.lang.foreign.FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT));
+ static final MethodHandle _get_osfhandle$MH =
+ downcallHandle("_get_osfhandle", java.lang.foreign.FunctionDescriptor.of(C_POINTER$LAYOUT, C_INT$LAYOUT));
+
+ public static final class INPUT_RECORD {
+ static final java.lang.foreign.MemoryLayout LAYOUT = java.lang.foreign.MemoryLayout.structLayout(
+ java.lang.foreign.ValueLayout.JAVA_SHORT.withName("EventType"),
+ java.lang.foreign.ValueLayout.JAVA_SHORT, // padding
+ java.lang.foreign.MemoryLayout.unionLayout(
+ KEY_EVENT_RECORD.LAYOUT.withName("KeyEvent"),
+ MOUSE_EVENT_RECORD.LAYOUT.withName("MouseEvent"),
+ WINDOW_BUFFER_SIZE_RECORD.LAYOUT.withName("WindowBufferSizeEvent"),
+ MENU_EVENT_RECORD.LAYOUT.withName("MenuEvent"),
+ FOCUS_EVENT_RECORD.LAYOUT.withName("FocusEvent"))
+ .withName("Event"));
+ static final VarHandle EventType$VH = varHandle(LAYOUT, "EventType");
+ static final long Event$OFFSET = byteOffset(LAYOUT, "Event");
+
+ private final java.lang.foreign.MemorySegment seg;
+
+ public INPUT_RECORD() {
+ this(java.lang.foreign.Arena.ofAuto());
+ }
+
+ public INPUT_RECORD(java.lang.foreign.Arena arena) {
+ this(arena.allocate(LAYOUT));
+ }
+
+ public INPUT_RECORD(java.lang.foreign.MemorySegment seg) {
+ this.seg = seg;
+ }
+
+ public short eventType() {
+ return (short) EventType$VH.get(seg);
+ }
+
+ public KEY_EVENT_RECORD keyEvent() {
+ return new KEY_EVENT_RECORD(seg, Event$OFFSET);
+ }
+
+ public MOUSE_EVENT_RECORD mouseEvent() {
+ return new MOUSE_EVENT_RECORD(seg, Event$OFFSET);
+ }
+
+ public FOCUS_EVENT_RECORD focusEvent() {
+ return new FOCUS_EVENT_RECORD(seg, Event$OFFSET);
+ }
+ }
+
+ public static final class MENU_EVENT_RECORD {
+
+ static final java.lang.foreign.GroupLayout LAYOUT =
+ java.lang.foreign.MemoryLayout.structLayout(C_DWORD$LAYOUT.withName("dwCommandId"));
+ static final VarHandle COMMAND_ID = varHandle(LAYOUT, "dwCommandId");
+
+ private final java.lang.foreign.MemorySegment seg;
+
+ public MENU_EVENT_RECORD() {
+ this(java.lang.foreign.Arena.ofAuto());
+ }
+
+ public MENU_EVENT_RECORD(java.lang.foreign.Arena arena) {
+ this(arena.allocate(LAYOUT));
+ }
+
+ public MENU_EVENT_RECORD(java.lang.foreign.MemorySegment seg) {
+ this.seg = seg;
+ }
+
+ public int commandId() {
+ return (int) MENU_EVENT_RECORD.COMMAND_ID.get(seg);
+ }
+
+ public void commandId(int commandId) {
+ MENU_EVENT_RECORD.COMMAND_ID.set(seg, commandId);
+ }
+ }
+
+ public static final class FOCUS_EVENT_RECORD {
+
+ static final java.lang.foreign.GroupLayout LAYOUT =
+ java.lang.foreign.MemoryLayout.structLayout(C_INT$LAYOUT.withName("bSetFocus"));
+ static final VarHandle SET_FOCUS = varHandle(LAYOUT, "bSetFocus");
+
+ private final java.lang.foreign.MemorySegment seg;
+
+ public FOCUS_EVENT_RECORD() {
+ this(java.lang.foreign.Arena.ofAuto());
+ }
+
+ public FOCUS_EVENT_RECORD(java.lang.foreign.Arena arena) {
+ this(arena.allocate(LAYOUT));
+ }
+
+ public FOCUS_EVENT_RECORD(java.lang.foreign.MemorySegment seg) {
+ this.seg = Objects.requireNonNull(seg);
+ }
+
+ public FOCUS_EVENT_RECORD(java.lang.foreign.MemorySegment seg, long offset) {
+ this.seg = Objects.requireNonNull(seg).asSlice(offset, LAYOUT.byteSize());
+ }
+
+ public boolean setFocus() {
+ return ((int) FOCUS_EVENT_RECORD.SET_FOCUS.get(seg) != 0);
+ }
+
+ public void setFocus(boolean setFocus) {
+ FOCUS_EVENT_RECORD.SET_FOCUS.set(seg, setFocus ? 1 : 0);
+ }
+ }
+
+ public static final class WINDOW_BUFFER_SIZE_RECORD {
+
+ static final java.lang.foreign.GroupLayout LAYOUT =
+ java.lang.foreign.MemoryLayout.structLayout(COORD.LAYOUT.withName("size"));
+ static final long SIZE_OFFSET = byteOffset(LAYOUT, "size");
+
+ private final java.lang.foreign.MemorySegment seg;
+
+ public WINDOW_BUFFER_SIZE_RECORD() {
+ this(java.lang.foreign.Arena.ofAuto());
+ }
+
+ public WINDOW_BUFFER_SIZE_RECORD(java.lang.foreign.Arena arena) {
+ this(arena.allocate(LAYOUT));
+ }
+
+ public WINDOW_BUFFER_SIZE_RECORD(java.lang.foreign.MemorySegment seg) {
+ this.seg = seg;
+ }
+
+ public COORD size() {
+ return new COORD(seg, SIZE_OFFSET);
+ }
+
+ public String toString() {
+ return "WINDOW_BUFFER_SIZE_RECORD{size=" + this.size() + '}';
+ }
+ }
+
+ public static final class MOUSE_EVENT_RECORD {
+
+ static final java.lang.foreign.MemoryLayout LAYOUT = java.lang.foreign.MemoryLayout.structLayout(
+ COORD.LAYOUT.withName("dwMousePosition"),
+ C_DWORD$LAYOUT.withName("dwButtonState"),
+ C_DWORD$LAYOUT.withName("dwControlKeyState"),
+ C_DWORD$LAYOUT.withName("dwEventFlags"));
+ static final long MOUSE_POSITION_OFFSET = byteOffset(LAYOUT, "dwMousePosition");
+ static final VarHandle BUTTON_STATE = varHandle(LAYOUT, "dwButtonState");
+ static final VarHandle CONTROL_KEY_STATE = varHandle(LAYOUT, "dwControlKeyState");
+ static final VarHandle EVENT_FLAGS = varHandle(LAYOUT, "dwEventFlags");
+
+ private final java.lang.foreign.MemorySegment seg;
+
+ public MOUSE_EVENT_RECORD() {
+ this(java.lang.foreign.Arena.ofAuto());
+ }
+
+ public MOUSE_EVENT_RECORD(java.lang.foreign.Arena arena) {
+ this(arena.allocate(LAYOUT));
+ }
+
+ public MOUSE_EVENT_RECORD(java.lang.foreign.MemorySegment seg) {
+ this.seg = Objects.requireNonNull(seg);
+ }
+
+ public MOUSE_EVENT_RECORD(java.lang.foreign.MemorySegment seg, long offset) {
+ this.seg = Objects.requireNonNull(seg).asSlice(offset, LAYOUT.byteSize());
+ }
+
+ public COORD mousePosition() {
+ return new COORD(seg, MOUSE_POSITION_OFFSET);
+ }
+
+ public int buttonState() {
+ return (int) BUTTON_STATE.get(seg);
+ }
+
+ public int controlKeyState() {
+ return (int) CONTROL_KEY_STATE.get(seg);
+ }
+
+ public int eventFlags() {
+ return (int) EVENT_FLAGS.get(seg);
+ }
+
+ public String toString() {
+ return "MOUSE_EVENT_RECORD{mousePosition=" + mousePosition() + ", buttonState=" + buttonState()
+ + ", controlKeyState=" + controlKeyState() + ", eventFlags=" + eventFlags() + '}';
+ }
+ }
+
+ public static final class KEY_EVENT_RECORD {
+
+ static final java.lang.foreign.MemoryLayout LAYOUT = java.lang.foreign.MemoryLayout.structLayout(
+ java.lang.foreign.ValueLayout.JAVA_INT.withName("bKeyDown"),
+ java.lang.foreign.ValueLayout.JAVA_SHORT.withName("wRepeatCount"),
+ java.lang.foreign.ValueLayout.JAVA_SHORT.withName("wVirtualKeyCode"),
+ java.lang.foreign.ValueLayout.JAVA_SHORT.withName("wVirtualScanCode"),
+ java.lang.foreign.MemoryLayout.unionLayout(
+ java.lang.foreign.ValueLayout.JAVA_CHAR.withName("UnicodeChar"),
+ java.lang.foreign.ValueLayout.JAVA_BYTE.withName("AsciiChar"))
+ .withName("uChar"),
+ java.lang.foreign.ValueLayout.JAVA_INT.withName("dwControlKeyState"));
+ static final VarHandle bKeyDown$VH = varHandle(LAYOUT, "bKeyDown");
+ static final VarHandle wRepeatCount$VH = varHandle(LAYOUT, "wRepeatCount");
+ static final VarHandle wVirtualKeyCode$VH = varHandle(LAYOUT, "wVirtualKeyCode");
+ static final VarHandle wVirtualScanCode$VH = varHandle(LAYOUT, "wVirtualScanCode");
+ static final VarHandle UnicodeChar$VH = varHandle(LAYOUT, "uChar", "UnicodeChar");
+ static final VarHandle AsciiChar$VH = varHandle(LAYOUT, "uChar", "AsciiChar");
+ static final VarHandle dwControlKeyState$VH = varHandle(LAYOUT, "dwControlKeyState");
+
+ final java.lang.foreign.MemorySegment seg;
+
+ public KEY_EVENT_RECORD() {
+ this(java.lang.foreign.Arena.ofAuto());
+ }
+
+ public KEY_EVENT_RECORD(java.lang.foreign.Arena arena) {
+ this(arena.allocate(LAYOUT));
+ }
+
+ public KEY_EVENT_RECORD(java.lang.foreign.MemorySegment seg) {
+ this.seg = seg;
+ }
+
+ public KEY_EVENT_RECORD(java.lang.foreign.MemorySegment seg, long offset) {
+ this.seg = Objects.requireNonNull(seg).asSlice(offset, LAYOUT.byteSize());
+ }
+
+ public boolean keyDown() {
+ return ((int) bKeyDown$VH.get(seg)) != 0;
+ }
+
+ public int repeatCount() {
+ return (int) wRepeatCount$VH.get(seg);
+ }
+
+ public short keyCode() {
+ return (short) wVirtualKeyCode$VH.get(seg);
+ }
+
+ public short scanCode() {
+ return (short) wVirtualScanCode$VH.get(seg);
+ }
+
+ public char uchar() {
+ return (char) UnicodeChar$VH.get(seg);
+ }
+
+ public int controlKeyState() {
+ return (int) dwControlKeyState$VH.get(seg);
+ }
+
+ public String toString() {
+ return "KEY_EVENT_RECORD{keyDown=" + this.keyDown() + ", repeatCount=" + this.repeatCount() + ", keyCode="
+ + this.keyCode() + ", scanCode=" + this.scanCode() + ", uchar=" + this.uchar()
+ + ", controlKeyState="
+ + this.controlKeyState() + '}';
+ }
+ }
+
+ public static final class CHAR_INFO {
+
+ static final java.lang.foreign.GroupLayout LAYOUT = java.lang.foreign.MemoryLayout.structLayout(
+ java.lang.foreign.MemoryLayout.unionLayout(
+ C_WCHAR$LAYOUT.withName("UnicodeChar"), C_CHAR$LAYOUT.withName("AsciiChar"))
+ .withName("Char"),
+ C_WORD$LAYOUT.withName("Attributes"));
+ static final VarHandle UnicodeChar$VH = varHandle(LAYOUT, "Char", "UnicodeChar");
+ static final VarHandle Attributes$VH = varHandle(LAYOUT, "Attributes");
+
+ final java.lang.foreign.MemorySegment seg;
+
+ public CHAR_INFO() {
+ this(java.lang.foreign.Arena.ofAuto());
+ }
+
+ public CHAR_INFO(java.lang.foreign.Arena arena) {
+ this(arena.allocate(LAYOUT));
+ }
+
+ public CHAR_INFO(java.lang.foreign.Arena arena, char c, short a) {
+ this(arena);
+ UnicodeChar$VH.set(seg, c);
+ Attributes$VH.set(seg, a);
+ }
+
+ public CHAR_INFO(java.lang.foreign.MemorySegment seg) {
+ this.seg = seg;
+ }
+
+ public char unicodeChar() {
+ return (char) UnicodeChar$VH.get(seg);
+ }
+ }
+
+ public static final class CONSOLE_SCREEN_BUFFER_INFO {
+ static final java.lang.foreign.GroupLayout LAYOUT = java.lang.foreign.MemoryLayout.structLayout(
+ COORD.LAYOUT.withName("dwSize"),
+ COORD.LAYOUT.withName("dwCursorPosition"),
+ C_WORD$LAYOUT.withName("wAttributes"),
+ SMALL_RECT.LAYOUT.withName("srWindow"),
+ COORD.LAYOUT.withName("dwMaximumWindowSize"));
+ static final long dwSize$OFFSET = byteOffset(LAYOUT, "dwSize");
+ static final long dwCursorPosition$OFFSET = byteOffset(LAYOUT, "dwCursorPosition");
+ static final VarHandle wAttributes$VH = varHandle(LAYOUT, "wAttributes");
+ static final long srWindow$OFFSET = byteOffset(LAYOUT, "srWindow");
+
+ private final java.lang.foreign.MemorySegment seg;
+
+ public CONSOLE_SCREEN_BUFFER_INFO() {
+ this(java.lang.foreign.Arena.ofAuto());
+ }
+
+ public CONSOLE_SCREEN_BUFFER_INFO(java.lang.foreign.Arena arena) {
+ this(arena.allocate(LAYOUT));
+ }
+
+ public CONSOLE_SCREEN_BUFFER_INFO(java.lang.foreign.MemorySegment seg) {
+ this.seg = seg;
+ }
+
+ public COORD size() {
+ return new COORD(seg, dwSize$OFFSET);
+ }
+
+ public COORD cursorPosition() {
+ return new COORD(seg, dwCursorPosition$OFFSET);
+ }
+
+ public short attributes() {
+ return (short) wAttributes$VH.get(seg);
+ }
+
+ public SMALL_RECT window() {
+ return new SMALL_RECT(seg, srWindow$OFFSET);
+ }
+
+ public int windowWidth() {
+ return this.window().width() + 1;
+ }
+
+ public int windowHeight() {
+ return this.window().height() + 1;
+ }
+
+ public void attributes(short attr) {
+ wAttributes$VH.set(seg, attr);
+ }
+ }
+
+ public static final class COORD {
+
+ static final java.lang.foreign.GroupLayout LAYOUT =
+ java.lang.foreign.MemoryLayout.structLayout(C_SHORT$LAYOUT.withName("x"), C_SHORT$LAYOUT.withName("y"));
+ static final VarHandle x$VH = varHandle(LAYOUT, "x");
+ static final VarHandle y$VH = varHandle(LAYOUT, "y");
+
+ private final java.lang.foreign.MemorySegment seg;
+
+ public COORD() {
+ this(java.lang.foreign.Arena.ofAuto());
+ }
+
+ public COORD(java.lang.foreign.Arena arena) {
+ this(arena.allocate(LAYOUT));
+ }
+
+ public COORD(java.lang.foreign.Arena arena, short x, short y) {
+ this(arena.allocate(LAYOUT));
+ x(x);
+ y(y);
+ }
+
+ public COORD(java.lang.foreign.MemorySegment seg) {
+ this.seg = seg;
+ }
+
+ public COORD(java.lang.foreign.MemorySegment seg, long offset) {
+ this.seg = Objects.requireNonNull(seg).asSlice(offset, LAYOUT.byteSize());
+ }
+
+ public short x() {
+ return (short) COORD.x$VH.get(seg);
+ }
+
+ public void x(short x) {
+ COORD.x$VH.set(seg, x);
+ }
+
+ public short y() {
+ return (short) COORD.y$VH.get(seg);
+ }
+
+ public void y(short y) {
+ COORD.y$VH.set(seg, y);
+ }
+
+ public COORD copy(java.lang.foreign.Arena arena) {
+ return new COORD(arena.allocate(LAYOUT).copyFrom(seg));
+ }
+ }
+
+ public static final class SMALL_RECT {
+
+ static final java.lang.foreign.GroupLayout LAYOUT = java.lang.foreign.MemoryLayout.structLayout(
+ C_SHORT$LAYOUT.withName("Left"),
+ C_SHORT$LAYOUT.withName("Top"),
+ C_SHORT$LAYOUT.withName("Right"),
+ C_SHORT$LAYOUT.withName("Bottom"));
+ static final VarHandle Left$VH = varHandle(LAYOUT, "Left");
+ static final VarHandle Top$VH = varHandle(LAYOUT, "Top");
+ static final VarHandle Right$VH = varHandle(LAYOUT, "Right");
+ static final VarHandle Bottom$VH = varHandle(LAYOUT, "Bottom");
+
+ private final java.lang.foreign.MemorySegment seg;
+
+ public SMALL_RECT() {
+ this(java.lang.foreign.Arena.ofAuto());
+ }
+
+ public SMALL_RECT(java.lang.foreign.Arena arena) {
+ this(arena.allocate(LAYOUT));
+ }
+
+ public SMALL_RECT(java.lang.foreign.Arena arena, SMALL_RECT rect) {
+ this(arena);
+ left(rect.left());
+ right(rect.right());
+ top(rect.top());
+ bottom(rect.bottom());
+ }
+
+ public SMALL_RECT(java.lang.foreign.MemorySegment seg, long offset) {
+ this(seg.asSlice(offset, LAYOUT.byteSize()));
+ }
+
+ public SMALL_RECT(java.lang.foreign.MemorySegment seg) {
+ this.seg = seg;
+ }
+
+ public short left() {
+ return (short) Left$VH.get(seg);
+ }
+
+ public short top() {
+ return (short) Top$VH.get(seg);
+ }
+
+ public short right() {
+ return (short) Right$VH.get(seg);
+ }
+
+ public short bottom() {
+ return (short) Bottom$VH.get(seg);
+ }
+
+ public short width() {
+ return (short) (this.right() - this.left());
+ }
+
+ public short height() {
+ return (short) (this.bottom() - this.top());
+ }
+
+ public void left(short l) {
+ Left$VH.set(seg, l);
+ }
+
+ public void top(short t) {
+ Top$VH.set(seg, t);
+ }
+
+ public void right(short r) {
+ Right$VH.set(seg, r);
+ }
+
+ public void bottom(short b) {
+ Bottom$VH.set(seg, b);
+ }
+
+ public SMALL_RECT copy(java.lang.foreign.Arena arena) {
+ return new SMALL_RECT(arena.allocate(LAYOUT).copyFrom(seg));
+ }
+ }
+
+ static T requireNonNull(T obj, String symbolName) {
+ if (obj == null) {
+ throw new UnsatisfiedLinkError("unresolved symbol: " + symbolName);
+ }
+ return obj;
+ }
+
+ static VarHandle varHandle(java.lang.foreign.MemoryLayout layout, String name) {
+ return layout.varHandle(java.lang.foreign.MemoryLayout.PathElement.groupElement(name));
+ }
+
+ static VarHandle varHandle(java.lang.foreign.MemoryLayout layout, String e1, String name) {
+ return layout.varHandle(
+ java.lang.foreign.MemoryLayout.PathElement.groupElement(e1),
+ java.lang.foreign.MemoryLayout.PathElement.groupElement(name));
+ }
+
+ static long byteOffset(java.lang.foreign.MemoryLayout layout, String name) {
+ return layout.byteOffset(java.lang.foreign.MemoryLayout.PathElement.groupElement(name));
+ }
+}
diff --git a/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/NativeWinConsoleWriter.java b/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/NativeWinConsoleWriter.java
new file mode 100644
index 000000000..38573f954
--- /dev/null
+++ b/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/NativeWinConsoleWriter.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2022-2023, the original author(s).
+ *
+ * This software is distributable under the BSD license. See the terms of the
+ * BSD license in the documentation provided with this software.
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+package org.jline.terminal.impl.ffm;
+
+import java.io.IOException;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.ValueLayout;
+
+import org.jline.terminal.impl.AbstractWindowsConsoleWriter;
+
+import static org.jline.terminal.impl.ffm.Kernel32.GetStdHandle;
+import static org.jline.terminal.impl.ffm.Kernel32.STD_OUTPUT_HANDLE;
+import static org.jline.terminal.impl.ffm.Kernel32.WriteConsoleW;
+import static org.jline.terminal.impl.ffm.Kernel32.getLastErrorMessage;
+
+@SuppressWarnings("preview")
+class NativeWinConsoleWriter extends AbstractWindowsConsoleWriter {
+
+ private final java.lang.foreign.MemorySegment console = GetStdHandle(STD_OUTPUT_HANDLE);
+
+ @Override
+ protected void writeConsole(char[] text, int len) throws IOException {
+ try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
+ java.lang.foreign.MemorySegment txt = arena.allocateArray(ValueLayout.JAVA_CHAR, text);
+ if (WriteConsoleW(console, txt, len, MemorySegment.NULL, MemorySegment.NULL) == 0) {
+ throw new IOException("Failed to write to console: " + getLastErrorMessage());
+ }
+ }
+ }
+}
diff --git a/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/NativeWinSysTerminal.java b/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/NativeWinSysTerminal.java
new file mode 100644
index 000000000..411f0768f
--- /dev/null
+++ b/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/NativeWinSysTerminal.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (c) 2022-2023, the original author(s).
+ *
+ * This software is distributable under the BSD license. See the terms of the
+ * BSD license in the documentation provided with this software.
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+package org.jline.terminal.impl.ffm;
+
+import java.io.BufferedWriter;
+import java.io.IOError;
+import java.io.IOException;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.util.function.IntConsumer;
+
+import org.jline.terminal.Cursor;
+import org.jline.terminal.Size;
+import org.jline.terminal.impl.AbstractWindowsTerminal;
+import org.jline.terminal.spi.TerminalProvider;
+import org.jline.utils.OSUtils;
+
+import static org.jline.terminal.impl.ffm.Kernel32.*;
+import static org.jline.terminal.impl.ffm.Kernel32.GetConsoleMode;
+import static org.jline.terminal.impl.ffm.Kernel32.GetConsoleScreenBufferInfo;
+import static org.jline.terminal.impl.ffm.Kernel32.GetStdHandle;
+import static org.jline.terminal.impl.ffm.Kernel32.INPUT_RECORD;
+import static org.jline.terminal.impl.ffm.Kernel32.INVALID_HANDLE_VALUE;
+import static org.jline.terminal.impl.ffm.Kernel32.KEY_EVENT_RECORD;
+import static org.jline.terminal.impl.ffm.Kernel32.MOUSE_EVENT_RECORD;
+import static org.jline.terminal.impl.ffm.Kernel32.STD_ERROR_HANDLE;
+import static org.jline.terminal.impl.ffm.Kernel32.STD_INPUT_HANDLE;
+import static org.jline.terminal.impl.ffm.Kernel32.STD_OUTPUT_HANDLE;
+import static org.jline.terminal.impl.ffm.Kernel32.SetConsoleMode;
+import static org.jline.terminal.impl.ffm.Kernel32.WaitForSingleObject;
+import static org.jline.terminal.impl.ffm.Kernel32.getLastErrorMessage;
+import static org.jline.terminal.impl.ffm.Kernel32.readConsoleInputHelper;
+
+@SuppressWarnings("preview")
+public class NativeWinSysTerminal extends AbstractWindowsTerminal {
+
+ public static NativeWinSysTerminal createTerminal(
+ String name,
+ String type,
+ boolean ansiPassThrough,
+ Charset encoding,
+ boolean nativeSignals,
+ SignalHandler signalHandler,
+ boolean paused,
+ TerminalProvider.Stream consoleStream)
+ throws IOException {
+ try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
+ // Get input console mode
+ java.lang.foreign.MemorySegment consoleIn = GetStdHandle(STD_INPUT_HANDLE);
+ java.lang.foreign.MemorySegment inMode = allocateInt(arena);
+ if (GetConsoleMode(consoleIn, inMode) == 0) {
+ throw new IOException("Failed to get console mode: " + getLastErrorMessage());
+ }
+ // Get output console and mode
+ java.lang.foreign.MemorySegment console;
+ switch (consoleStream) {
+ case Output:
+ console = GetStdHandle(STD_OUTPUT_HANDLE);
+ break;
+ case Error:
+ console = GetStdHandle(STD_ERROR_HANDLE);
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupport stream for console: " + consoleStream);
+ }
+ java.lang.foreign.MemorySegment outMode = allocateInt(arena);
+ if (GetConsoleMode(console, outMode) == 0) {
+ throw new IOException("Failed to get console mode: " + getLastErrorMessage());
+ }
+ // Create writer
+ Writer writer;
+ if (ansiPassThrough) {
+ type = type != null ? type : OSUtils.IS_CONEMU ? TYPE_WINDOWS_CONEMU : TYPE_WINDOWS;
+ writer = new NativeWinConsoleWriter();
+ } else {
+ int m = inMode.get(java.lang.foreign.ValueLayout.JAVA_INT, 0);
+ if (enableVtp(console, m)) {
+ type = type != null ? type : TYPE_WINDOWS_VTP;
+ writer = new NativeWinConsoleWriter();
+ } else if (OSUtils.IS_CONEMU) {
+ type = type != null ? type : TYPE_WINDOWS_CONEMU;
+ writer = new NativeWinConsoleWriter();
+ } else {
+ type = type != null ? type : TYPE_WINDOWS;
+ writer = new WindowsAnsiWriter(new BufferedWriter(new NativeWinConsoleWriter()));
+ }
+ }
+ // Create terminal
+ NativeWinSysTerminal terminal = new NativeWinSysTerminal(
+ writer,
+ name,
+ type,
+ encoding,
+ nativeSignals,
+ signalHandler,
+ consoleIn,
+ inMode.get(java.lang.foreign.ValueLayout.JAVA_INT, 0),
+ console,
+ outMode.get(java.lang.foreign.ValueLayout.JAVA_INT, 0));
+ // Start input pump thread
+ if (!paused) {
+ terminal.resume();
+ }
+ return terminal;
+ }
+ }
+
+ private static boolean enableVtp(java.lang.foreign.MemorySegment console, int m) {
+ return SetConsoleMode(console, m | AbstractWindowsTerminal.ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0;
+ }
+
+ public static boolean isWindowsSystemStream(TerminalProvider.Stream stream) {
+ try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
+ java.lang.foreign.MemorySegment console;
+ java.lang.foreign.MemorySegment mode = allocateInt(arena);
+ switch (stream) {
+ case Input:
+ console = GetStdHandle(STD_INPUT_HANDLE);
+ break;
+ case Output:
+ console = GetStdHandle(STD_OUTPUT_HANDLE);
+ break;
+ case Error:
+ console = GetStdHandle(STD_ERROR_HANDLE);
+ break;
+ default:
+ return false;
+ }
+ return GetConsoleMode(console, mode) != 0;
+ }
+ }
+
+ private static java.lang.foreign.MemorySegment allocateInt(java.lang.foreign.Arena arena) {
+ return arena.allocate(java.lang.foreign.ValueLayout.JAVA_INT);
+ }
+
+ NativeWinSysTerminal(
+ Writer writer,
+ String name,
+ String type,
+ Charset encoding,
+ boolean nativeSignals,
+ SignalHandler signalHandler,
+ java.lang.foreign.MemorySegment inConsole,
+ int inConsoleMode,
+ java.lang.foreign.MemorySegment outConsole,
+ int outConsoleMode)
+ throws IOException {
+ super(
+ writer,
+ name,
+ type,
+ encoding,
+ nativeSignals,
+ signalHandler,
+ inConsole,
+ inConsoleMode,
+ outConsole,
+ outConsoleMode);
+ }
+
+ @Override
+ protected int getConsoleMode(java.lang.foreign.MemorySegment console) {
+ try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
+ java.lang.foreign.MemorySegment mode = arena.allocate(java.lang.foreign.ValueLayout.JAVA_INT);
+ if (GetConsoleMode(console, mode) == 0) {
+ return -1;
+ }
+ return mode.get(java.lang.foreign.ValueLayout.JAVA_INT, 0);
+ }
+ }
+
+ @Override
+ protected void setConsoleMode(java.lang.foreign.MemorySegment console, int mode) {
+ SetConsoleMode(console, mode);
+ }
+
+ public Size getSize() {
+ try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
+ CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO(arena);
+ GetConsoleScreenBufferInfo(outConsole, info);
+ return new Size(info.windowWidth(), info.windowHeight());
+ }
+ }
+
+ @Override
+ public Size getBufferSize() {
+ try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
+ CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO(arena);
+ GetConsoleScreenBufferInfo(outConsole, info);
+ return new Size(info.size().x(), info.size().y());
+ }
+ }
+
+ protected boolean processConsoleInput() throws IOException {
+ try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
+ INPUT_RECORD[] events;
+ if (inConsole != null
+ && inConsole.address() != INVALID_HANDLE_VALUE
+ && WaitForSingleObject(inConsole, 100) == 0) {
+ events = readConsoleInputHelper(arena, inConsole, 1, false);
+ } else {
+ return false;
+ }
+
+ boolean flush = false;
+ for (INPUT_RECORD event : events) {
+ int eventType = event.eventType();
+ if (eventType == KEY_EVENT) {
+ KEY_EVENT_RECORD keyEvent = event.keyEvent();
+ processKeyEvent(
+ keyEvent.keyDown(), keyEvent.keyCode(), keyEvent.uchar(), keyEvent.controlKeyState());
+ flush = true;
+ } else if (eventType == WINDOW_BUFFER_SIZE_EVENT) {
+ raise(Signal.WINCH);
+ } else if (eventType == MOUSE_EVENT) {
+ processMouseEvent(event.mouseEvent());
+ flush = true;
+ } else if (eventType == FOCUS_EVENT) {
+ processFocusEvent(event.focusEvent().setFocus());
+ }
+ }
+
+ return flush;
+ }
+ }
+
+ private final char[] focus = new char[] {'\033', '[', ' '};
+
+ private void processFocusEvent(boolean hasFocus) throws IOException {
+ if (focusTracking) {
+ focus[2] = hasFocus ? 'I' : 'O';
+ slaveInputPipe.write(focus);
+ }
+ }
+
+ private final char[] mouse = new char[] {'\033', '[', 'M', ' ', ' ', ' '};
+
+ private void processMouseEvent(MOUSE_EVENT_RECORD mouseEvent) throws IOException {
+ int dwEventFlags = mouseEvent.eventFlags();
+ int dwButtonState = mouseEvent.buttonState();
+ if (tracking == MouseTracking.Off
+ || tracking == MouseTracking.Normal && dwEventFlags == MOUSE_MOVED
+ || tracking == MouseTracking.Button && dwEventFlags == MOUSE_MOVED && dwButtonState == 0) {
+ return;
+ }
+ int cb = 0;
+ dwEventFlags &= ~DOUBLE_CLICK; // Treat double-clicks as normal
+ if (dwEventFlags == MOUSE_WHEELED) {
+ cb |= 64;
+ if ((dwButtonState >> 16) < 0) {
+ cb |= 1;
+ }
+ } else if (dwEventFlags == MOUSE_HWHEELED) {
+ return;
+ } else if ((dwButtonState & FROM_LEFT_1ST_BUTTON_PRESSED) != 0) {
+ cb |= 0x00;
+ } else if ((dwButtonState & RIGHTMOST_BUTTON_PRESSED) != 0) {
+ cb |= 0x01;
+ } else if ((dwButtonState & FROM_LEFT_2ND_BUTTON_PRESSED) != 0) {
+ cb |= 0x02;
+ } else {
+ cb |= 0x03;
+ }
+ int cx = mouseEvent.mousePosition().x();
+ int cy = mouseEvent.mousePosition().y();
+ mouse[3] = (char) (' ' + cb);
+ mouse[4] = (char) (' ' + cx + 1);
+ mouse[5] = (char) (' ' + cy + 1);
+ slaveInputPipe.write(mouse);
+ }
+
+ @Override
+ public Cursor getCursorPosition(IntConsumer discarded) {
+ try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
+ CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO(arena);
+ if (GetConsoleScreenBufferInfo(outConsole, info) == 0) {
+ throw new IOError(new IOException("Could not get the cursor position: " + getLastErrorMessage()));
+ }
+ return new Cursor(info.cursorPosition().x(), info.cursorPosition().y());
+ }
+ }
+}
diff --git a/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/WindowsAnsiWriter.java b/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/WindowsAnsiWriter.java
new file mode 100644
index 000000000..ea6b89d67
--- /dev/null
+++ b/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/WindowsAnsiWriter.java
@@ -0,0 +1,408 @@
+/*
+ * Copyright (c) 2022-2023, the original author(s).
+ *
+ * This software is distributable under the BSD license. See the terms of the
+ * BSD license in the documentation provided with this software.
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+package org.jline.terminal.impl.ffm;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.jline.utils.AnsiWriter;
+import org.jline.utils.Colors;
+
+import static org.jline.terminal.impl.ffm.Kernel32.BACKGROUND_BLUE;
+import static org.jline.terminal.impl.ffm.Kernel32.BACKGROUND_GREEN;
+import static org.jline.terminal.impl.ffm.Kernel32.BACKGROUND_INTENSITY;
+import static org.jline.terminal.impl.ffm.Kernel32.BACKGROUND_RED;
+import static org.jline.terminal.impl.ffm.Kernel32.CHAR_INFO;
+import static org.jline.terminal.impl.ffm.Kernel32.CONSOLE_SCREEN_BUFFER_INFO;
+import static org.jline.terminal.impl.ffm.Kernel32.COORD;
+import static org.jline.terminal.impl.ffm.Kernel32.FOREGROUND_BLUE;
+import static org.jline.terminal.impl.ffm.Kernel32.FOREGROUND_GREEN;
+import static org.jline.terminal.impl.ffm.Kernel32.FOREGROUND_INTENSITY;
+import static org.jline.terminal.impl.ffm.Kernel32.FOREGROUND_RED;
+import static org.jline.terminal.impl.ffm.Kernel32.FillConsoleOutputAttribute;
+import static org.jline.terminal.impl.ffm.Kernel32.FillConsoleOutputCharacterW;
+import static org.jline.terminal.impl.ffm.Kernel32.GetConsoleScreenBufferInfo;
+import static org.jline.terminal.impl.ffm.Kernel32.GetStdHandle;
+import static org.jline.terminal.impl.ffm.Kernel32.SMALL_RECT;
+import static org.jline.terminal.impl.ffm.Kernel32.STD_OUTPUT_HANDLE;
+import static org.jline.terminal.impl.ffm.Kernel32.ScrollConsoleScreenBuffer;
+import static org.jline.terminal.impl.ffm.Kernel32.SetConsoleCursorPosition;
+import static org.jline.terminal.impl.ffm.Kernel32.SetConsoleTextAttribute;
+import static org.jline.terminal.impl.ffm.Kernel32.SetConsoleTitleW;
+import static org.jline.terminal.impl.ffm.Kernel32.getLastErrorMessage;
+
+@SuppressWarnings("preview")
+class WindowsAnsiWriter extends AnsiWriter {
+
+ private static final java.lang.foreign.MemorySegment console = GetStdHandle(STD_OUTPUT_HANDLE);
+
+ private static final short FOREGROUND_BLACK = 0;
+ private static final short FOREGROUND_YELLOW = (short) (FOREGROUND_RED | FOREGROUND_GREEN);
+ private static final short FOREGROUND_MAGENTA = (short) (FOREGROUND_BLUE | FOREGROUND_RED);
+ private static final short FOREGROUND_CYAN = (short) (FOREGROUND_BLUE | FOREGROUND_GREEN);
+ private static final short FOREGROUND_WHITE = (short) (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
+
+ private static final short BACKGROUND_BLACK = 0;
+ private static final short BACKGROUND_YELLOW = (short) (BACKGROUND_RED | BACKGROUND_GREEN);
+ private static final short BACKGROUND_MAGENTA = (short) (BACKGROUND_BLUE | BACKGROUND_RED);
+ private static final short BACKGROUND_CYAN = (short) (BACKGROUND_BLUE | BACKGROUND_GREEN);
+ private static final short BACKGROUND_WHITE = (short) (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE);
+
+ private static final short[] ANSI_FOREGROUND_COLOR_MAP = {
+ FOREGROUND_BLACK,
+ FOREGROUND_RED,
+ FOREGROUND_GREEN,
+ FOREGROUND_YELLOW,
+ FOREGROUND_BLUE,
+ FOREGROUND_MAGENTA,
+ FOREGROUND_CYAN,
+ FOREGROUND_WHITE,
+ };
+
+ private static final short[] ANSI_BACKGROUND_COLOR_MAP = {
+ BACKGROUND_BLACK,
+ BACKGROUND_RED,
+ BACKGROUND_GREEN,
+ BACKGROUND_YELLOW,
+ BACKGROUND_BLUE,
+ BACKGROUND_MAGENTA,
+ BACKGROUND_CYAN,
+ BACKGROUND_WHITE,
+ };
+
+ private final CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO(java.lang.foreign.Arena.ofAuto());
+ private final short originalColors;
+
+ private boolean negative;
+ private boolean bold;
+ private boolean underline;
+ private short savedX = -1;
+ private short savedY = -1;
+
+ public WindowsAnsiWriter(Writer out) throws IOException {
+ super(out);
+ getConsoleInfo();
+ originalColors = info.attributes();
+ }
+
+ private void getConsoleInfo() throws IOException {
+ out.flush();
+ if (GetConsoleScreenBufferInfo(console, info) == 0) {
+ throw new IOException("Could not get the screen info: " + getLastErrorMessage());
+ }
+ if (negative) {
+ info.attributes(invertAttributeColors(info.attributes()));
+ }
+ }
+
+ private void applyAttribute() throws IOException {
+ out.flush();
+ short attributes = info.attributes();
+ // bold is simulated by high foreground intensity
+ if (bold) {
+ attributes |= FOREGROUND_INTENSITY;
+ }
+ // underline is simulated by high foreground intensity
+ if (underline) {
+ attributes |= BACKGROUND_INTENSITY;
+ }
+ if (negative) {
+ attributes = invertAttributeColors(attributes);
+ }
+ if (SetConsoleTextAttribute(console, attributes) == 0) {
+ throw new IOException(getLastErrorMessage());
+ }
+ }
+
+ private short invertAttributeColors(short attributes) {
+ // Swap the the Foreground and Background bits.
+ int fg = 0x000F & attributes;
+ fg <<= 4;
+ int bg = 0X00F0 & attributes;
+ bg >>= 4;
+ attributes = (short) ((attributes & 0xFF00) | fg | bg);
+ return attributes;
+ }
+
+ private void applyCursorPosition() throws IOException {
+ info.cursorPosition().x((short)
+ Math.max(0, Math.min(info.size().x() - 1, info.cursorPosition().x())));
+ info.cursorPosition().y((short)
+ Math.max(0, Math.min(info.size().y() - 1, info.cursorPosition().y())));
+ if (SetConsoleCursorPosition(console, info.cursorPosition()) == 0) {
+ throw new IOException(getLastErrorMessage());
+ }
+ }
+
+ @Override
+ protected void processEraseScreen(int eraseOption) throws IOException {
+ getConsoleInfo();
+ try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
+ java.lang.foreign.MemorySegment written = arena.allocate(java.lang.foreign.ValueLayout.JAVA_INT);
+ switch (eraseOption) {
+ case ERASE_SCREEN -> {
+ COORD topLeft = new COORD(arena, (short) 0, info.window().top());
+ int screenLength = info.window().height() * info.size().x();
+ FillConsoleOutputAttribute(console, originalColors, screenLength, topLeft, written);
+ FillConsoleOutputCharacterW(console, ' ', screenLength, topLeft, written);
+ }
+ case ERASE_SCREEN_TO_BEGINING -> {
+ COORD topLeft2 = new COORD(arena, (short) 0, info.window().top());
+ int lengthToCursor =
+ (info.cursorPosition().y() - info.window().top())
+ * info.size().x()
+ + info.cursorPosition().x();
+ FillConsoleOutputAttribute(console, originalColors, lengthToCursor, topLeft2, written);
+ FillConsoleOutputCharacterW(console, ' ', lengthToCursor, topLeft2, written);
+ }
+ case ERASE_SCREEN_TO_END -> {
+ int lengthToEnd =
+ (info.window().bottom() - info.cursorPosition().y())
+ * info.size().x()
+ + (info.size().x() - info.cursorPosition().x());
+ FillConsoleOutputAttribute(console, originalColors, lengthToEnd, info.cursorPosition(), written);
+ FillConsoleOutputCharacterW(console, ' ', lengthToEnd, info.cursorPosition(), written);
+ }
+ default -> {}
+ }
+ }
+ }
+
+ @Override
+ protected void processEraseLine(int eraseOption) throws IOException {
+ getConsoleInfo();
+ try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
+ java.lang.foreign.MemorySegment written = arena.allocate(java.lang.foreign.ValueLayout.JAVA_INT);
+ switch (eraseOption) {
+ case ERASE_LINE -> {
+ COORD leftColCurrRow =
+ new COORD(arena, (short) 0, info.cursorPosition().y());
+ FillConsoleOutputAttribute(
+ console, originalColors, info.size().x(), leftColCurrRow, written);
+ FillConsoleOutputCharacterW(console, ' ', info.size().x(), leftColCurrRow, written);
+ }
+ case ERASE_LINE_TO_BEGINING -> {
+ COORD leftColCurrRow2 =
+ new COORD(arena, (short) 0, info.cursorPosition().y());
+ FillConsoleOutputAttribute(
+ console, originalColors, info.cursorPosition().x(), leftColCurrRow2, written);
+ FillConsoleOutputCharacterW(
+ console, ' ', info.cursorPosition().x(), leftColCurrRow2, written);
+ }
+ case ERASE_LINE_TO_END -> {
+ int lengthToLastCol =
+ info.size().x() - info.cursorPosition().x();
+ FillConsoleOutputAttribute(
+ console, originalColors, lengthToLastCol, info.cursorPosition(), written);
+ FillConsoleOutputCharacterW(console, ' ', lengthToLastCol, info.cursorPosition(), written);
+ }
+ default -> {}
+ }
+ }
+ }
+
+ protected void processCursorUpLine(int count) throws IOException {
+ getConsoleInfo();
+ info.cursorPosition().x((short) 0);
+ info.cursorPosition().y((short) (info.cursorPosition().y() - count));
+ applyCursorPosition();
+ }
+
+ protected void processCursorDownLine(int count) throws IOException {
+ getConsoleInfo();
+ info.cursorPosition().x((short) 0);
+ info.cursorPosition().y((short) (info.cursorPosition().y() + count));
+ applyCursorPosition();
+ }
+
+ @Override
+ protected void processCursorLeft(int count) throws IOException {
+ getConsoleInfo();
+ info.cursorPosition().x((short) (info.cursorPosition().x() - count));
+ applyCursorPosition();
+ }
+
+ @Override
+ protected void processCursorRight(int count) throws IOException {
+ getConsoleInfo();
+ info.cursorPosition().x((short) (info.cursorPosition().x() + count));
+ applyCursorPosition();
+ }
+
+ @Override
+ protected void processCursorDown(int count) throws IOException {
+ getConsoleInfo();
+ int nb = Math.max(0, info.cursorPosition().y() + count - info.size().y() + 1);
+ if (nb != count) {
+ info.cursorPosition().y((short) (info.cursorPosition().y() + count));
+ applyCursorPosition();
+ }
+ if (nb > 0) {
+ try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
+ SMALL_RECT scroll = new SMALL_RECT(arena, info.window());
+ scroll.top((short) 0);
+ COORD org = new COORD(arena);
+ org.x((short) 0);
+ org.y((short) (-nb));
+ CHAR_INFO info = new CHAR_INFO(arena, ' ', originalColors);
+ ScrollConsoleScreenBuffer(console, scroll, scroll, org, info);
+ }
+ }
+ }
+
+ @Override
+ protected void processCursorUp(int count) throws IOException {
+ getConsoleInfo();
+ info.cursorPosition().y((short) (info.cursorPosition().y() - count));
+ applyCursorPosition();
+ }
+
+ @Override
+ protected void processCursorTo(int row, int col) throws IOException {
+ getConsoleInfo();
+ info.cursorPosition().y((short) (info.window().top() + row - 1));
+ info.cursorPosition().x((short) (col - 1));
+ applyCursorPosition();
+ }
+
+ @Override
+ protected void processCursorToColumn(int x) throws IOException {
+ getConsoleInfo();
+ info.cursorPosition().x((short) (x - 1));
+ applyCursorPosition();
+ }
+
+ @Override
+ protected void processSetForegroundColorExt(int paletteIndex) throws IOException {
+ int color = Colors.roundColor(paletteIndex, 16);
+ info.attributes((short) ((info.attributes() & ~0x0007) | ANSI_FOREGROUND_COLOR_MAP[color & 0x07]));
+ info.attributes(
+ (short) ((info.attributes() & ~FOREGROUND_INTENSITY) | (color >= 8 ? FOREGROUND_INTENSITY : 0)));
+ applyAttribute();
+ }
+
+ @Override
+ protected void processSetBackgroundColorExt(int paletteIndex) throws IOException {
+ int color = Colors.roundColor(paletteIndex, 16);
+ info.attributes((short) ((info.attributes() & ~0x0070) | ANSI_BACKGROUND_COLOR_MAP[color & 0x07]));
+ info.attributes(
+ (short) ((info.attributes() & ~BACKGROUND_INTENSITY) | (color >= 8 ? BACKGROUND_INTENSITY : 0)));
+ applyAttribute();
+ }
+
+ @Override
+ protected void processDefaultTextColor() throws IOException {
+ info.attributes((short) ((info.attributes() & ~0x000F) | (originalColors & 0xF)));
+ info.attributes((short) (info.attributes() & ~FOREGROUND_INTENSITY));
+ applyAttribute();
+ }
+
+ @Override
+ protected void processDefaultBackgroundColor() throws IOException {
+ info.attributes((short) ((info.attributes() & ~0x00F0) | (originalColors & 0xF0)));
+ info.attributes((short) (info.attributes() & ~BACKGROUND_INTENSITY));
+ applyAttribute();
+ }
+
+ @Override
+ protected void processAttributeRest() throws IOException {
+ info.attributes((short) ((info.attributes() & ~0x00FF) | originalColors));
+ this.negative = false;
+ this.bold = false;
+ this.underline = false;
+ applyAttribute();
+ }
+
+ @Override
+ protected void processSetAttribute(int attribute) throws IOException {
+ switch (attribute) {
+ case ATTRIBUTE_INTENSITY_BOLD -> {
+ bold = true;
+ applyAttribute();
+ }
+ case ATTRIBUTE_INTENSITY_NORMAL -> {
+ bold = false;
+ applyAttribute();
+ }
+ case ATTRIBUTE_UNDERLINE -> {
+ underline = true;
+ applyAttribute();
+ }
+ case ATTRIBUTE_UNDERLINE_OFF -> {
+ underline = false;
+ applyAttribute();
+ }
+ case ATTRIBUTE_NEGATIVE_ON -> {
+ negative = true;
+ applyAttribute();
+ }
+ case ATTRIBUTE_NEGATIVE_OFF -> {
+ negative = false;
+ applyAttribute();
+ }
+ default -> {}
+ }
+ }
+
+ @Override
+ protected void processSaveCursorPosition() throws IOException {
+ getConsoleInfo();
+ savedX = info.cursorPosition().x();
+ savedY = info.cursorPosition().y();
+ }
+
+ @Override
+ protected void processRestoreCursorPosition() throws IOException {
+ // restore only if there was a save operation first
+ if (savedX != -1 && savedY != -1) {
+ out.flush();
+ info.cursorPosition().x(savedX);
+ info.cursorPosition().y(savedY);
+ applyCursorPosition();
+ }
+ }
+
+ @Override
+ protected void processInsertLine(int optionInt) throws IOException {
+ try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
+ getConsoleInfo();
+ SMALL_RECT scroll = info.window().copy(arena);
+ scroll.top(info.cursorPosition().y());
+ COORD org =
+ new COORD(arena, (short) 0, (short) (info.cursorPosition().y() + optionInt));
+ CHAR_INFO info = new CHAR_INFO(arena, ' ', originalColors);
+ if (ScrollConsoleScreenBuffer(console, scroll, scroll, org, info) == 0) {
+ throw new IOException(getLastErrorMessage());
+ }
+ }
+ }
+
+ @Override
+ protected void processDeleteLine(int optionInt) throws IOException {
+ try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
+ getConsoleInfo();
+ SMALL_RECT scroll = info.window().copy(arena);
+ scroll.top(info.cursorPosition().y());
+ COORD org =
+ new COORD(arena, (short) 0, (short) (info.cursorPosition().y() - optionInt));
+ CHAR_INFO info = new CHAR_INFO(arena, ' ', originalColors);
+ if (ScrollConsoleScreenBuffer(console, scroll, scroll, org, info) == 0) {
+ throw new IOException(getLastErrorMessage());
+ }
+ }
+ }
+
+ @Override
+ protected void processChangeWindowTitle(String title) {
+ try (java.lang.foreign.Arena session = java.lang.foreign.Arena.ofConfined()) {
+ java.lang.foreign.MemorySegment str = session.allocateUtf8String(title);
+ SetConsoleTitleW(str);
+ }
+ }
+}
diff --git a/terminal-ffm/src/main/resources/META-INF/services/org/jline/terminal/provider/ffm b/terminal-ffm/src/main/resources/META-INF/services/org/jline/terminal/provider/ffm
new file mode 100644
index 000000000..0932a413b
--- /dev/null
+++ b/terminal-ffm/src/main/resources/META-INF/services/org/jline/terminal/provider/ffm
@@ -0,0 +1,16 @@
+#
+# Copyright (C) 2022 the original author(s).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+class = org.jline.terminal.impl.ffm.FfmTerminalProvider
diff --git a/terminal-ffm/src/test/java/org/jline/terminal/impl/ffm/FfmTest.java b/terminal-ffm/src/test/java/org/jline/terminal/impl/ffm/FfmTest.java
new file mode 100644
index 000000000..90f14362a
--- /dev/null
+++ b/terminal-ffm/src/test/java/org/jline/terminal/impl/ffm/FfmTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2023, the original author(s).
+ *
+ * This software is distributable under the BSD license. See the terms of the
+ * BSD license in the documentation provided with this software.
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+package org.jline.terminal.impl.ffm;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.Charset;
+
+import org.jline.terminal.Attributes;
+import org.jline.terminal.Size;
+import org.jline.terminal.Terminal;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import org.junit.jupiter.api.condition.EnabledOnOs;
+import org.junit.jupiter.api.condition.OS;
+
+public class FfmTest {
+
+ @Test
+ @DisabledOnOs(OS.WINDOWS) // non system terminals are not supported on windows
+ public void testNewTerminalWithNull() throws IOException {
+ Terminal terminal = new FfmTerminalProvider()
+ .newTerminal(
+ "name",
+ "xterm",
+ new ByteArrayInputStream(new byte[0]),
+ new ByteArrayOutputStream(),
+ Charset.defaultCharset(),
+ Terminal.SignalHandler.SIG_DFL,
+ false,
+ null,
+ null);
+ // terminal.close();
+ }
+
+ @Test
+ @DisabledOnOs(OS.WINDOWS) // non system terminals are not supported on windows
+ public void testNewTerminalNoNull() throws IOException {
+ Terminal terminal = new FfmTerminalProvider()
+ .newTerminal(
+ "name",
+ "xterm",
+ new ByteArrayInputStream(new byte[0]),
+ new ByteArrayOutputStream(),
+ Charset.defaultCharset(),
+ Terminal.SignalHandler.SIG_DFL,
+ false,
+ new Attributes(),
+ new Size());
+ Size size = terminal.getSize();
+ // terminal.close();
+ }
+
+ @Test
+ @EnabledOnOs(OS.WINDOWS)
+ void checkStructLayout() {
+ try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) {
+ new Kernel32.KEY_EVENT_RECORD(arena);
+ new Kernel32.MOUSE_EVENT_RECORD(arena);
+ new Kernel32.WINDOW_BUFFER_SIZE_RECORD(arena);
+ new Kernel32.MENU_EVENT_RECORD(arena);
+ new Kernel32.FOCUS_EVENT_RECORD(arena);
+ new Kernel32.INPUT_RECORD(arena);
+ new Kernel32.SMALL_RECT(arena);
+ }
+ }
+}
diff --git a/terminal/pom.xml b/terminal/pom.xml
index 06f4eda41..92137dfa2 100644
--- a/terminal/pom.xml
+++ b/terminal/pom.xml
@@ -24,6 +24,7 @@
org.jline.terminal
+ --enable-preview --release 21
diff --git a/terminal/src/main/java/org/jline/terminal/TerminalBuilder.java b/terminal/src/main/java/org/jline/terminal/TerminalBuilder.java
index 8797f7845..d1fdae166 100644
--- a/terminal/src/main/java/org/jline/terminal/TerminalBuilder.java
+++ b/terminal/src/main/java/org/jline/terminal/TerminalBuilder.java
@@ -50,10 +50,11 @@ public final class TerminalBuilder {
public static final String PROP_CODEPAGE = "org.jline.terminal.codepage";
public static final String PROP_TYPE = "org.jline.terminal.type";
public static final String PROP_PROVIDERS = "org.jline.terminal.providers";
- public static final String PROP_PROVIDERS_DEFAULT = "jansi,jna,exec";
+ public static final String PROP_PROVIDERS_DEFAULT = "ffm,jansi,jna,exec";
public static final String PROP_JNA = "org.jline.terminal.jna";
public static final String PROP_JANSI = "org.jline.terminal.jansi";
public static final String PROP_EXEC = "org.jline.terminal.exec";
+ public static final String PROP_FFM = "org.jline.terminal.ffm";
public static final String PROP_DUMB = "org.jline.terminal.dumb";
public static final String PROP_DUMB_COLOR = "org.jline.terminal.dumb.color";
public static final String PROP_OUTPUT = "org.jline.terminal.output";
@@ -138,6 +139,7 @@ public static TerminalBuilder builder() {
private Boolean jna;
private Boolean jansi;
private Boolean exec;
+ private Boolean ffm;
private Boolean dumb;
private Boolean color;
private Attributes attributes;
@@ -193,6 +195,11 @@ public TerminalBuilder exec(boolean exec) {
return this;
}
+ public TerminalBuilder ffm(boolean ffm) {
+ this.ffm = ffm;
+ return this;
+ }
+
public TerminalBuilder dumb(boolean dumb) {
this.dumb = dumb;
return this;
@@ -377,15 +384,30 @@ private Terminal doBuild() throws IOException {
if (exec == null) {
exec = getBoolean(PROP_EXEC, true);
}
+ Boolean ffm = this.ffm;
+ if (ffm == null) {
+ ffm = getBoolean(PROP_FFM, true);
+ }
Boolean dumb = this.dumb;
if (dumb == null) {
dumb = getBoolean(PROP_DUMB, null);
}
IllegalStateException exception = new IllegalStateException("Unable to create a terminal");
List providers = new ArrayList<>();
+ if (ffm) {
+ try {
+ TerminalProvider provider = TerminalProvider.load("ffm");
+ provider.isSystemStream(TerminalProvider.Stream.Output);
+ providers.add(provider);
+ } catch (Throwable t) {
+ Log.debug("Unable to load FFM support: ", t);
+ exception.addSuppressed(t);
+ }
+ }
if (jna) {
try {
TerminalProvider provider = TerminalProvider.load("jna");
+ provider.isSystemStream(TerminalProvider.Stream.Output);
providers.add(provider);
} catch (Throwable t) {
Log.debug("Unable to load JNA support: ", t);
@@ -395,6 +417,7 @@ private Terminal doBuild() throws IOException {
if (jansi) {
try {
TerminalProvider provider = TerminalProvider.load("jansi");
+ provider.isSystemStream(TerminalProvider.Stream.Output);
providers.add(provider);
} catch (Throwable t) {
Log.debug("Unable to load JANSI support: ", t);
diff --git a/terminal/src/main/java/org/jline/terminal/impl/Diag.java b/terminal/src/main/java/org/jline/terminal/impl/Diag.java
index 1b6133fd0..6bca5cc1b 100644
--- a/terminal/src/main/java/org/jline/terminal/impl/Diag.java
+++ b/terminal/src/main/java/org/jline/terminal/impl/Diag.java
@@ -50,6 +50,17 @@ static void diag(PrintStream out) {
out.println("IS_OSX = " + OSUtils.IS_OSX);
out.println();
+ // FFM
+ out.println("FFM Support");
+ out.println("=================");
+ try {
+ TerminalProvider provider = TerminalProvider.load("ffm");
+ testProvider(out, provider);
+ } catch (Throwable t) {
+ out.println("FFM support not available: " + t);
+ }
+ out.println();
+
out.println("JnaSupport");
out.println("=================");
try {