From 61c6f786c15cef73d94ddb2da4cb24c1865fe3c1 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Tue, 14 Jun 2022 18:53:55 +0200 Subject: [PATCH] JEP442 support --- jline/pom.xml | 20 + reader/pom.xml | 1 + .../org/jline/reader/impl/LineReaderImpl.java | 4 +- .../java/org/jline/reader/impl/UndoTree.java | 1 + .../reader/impl/history/DefaultHistory.java | 1 + terminal/pom.xml | 35 + .../java/org/jline/terminal/Attributes.java | 1 + .../main/java/org/jline/terminal/Size.java | 1 + .../org/jline/terminal/TerminalBuilder.java | 20 + .../jline/terminal/impl/AbstractTerminal.java | 4 +- .../impl/AbstractWindowsTerminal.java | 1 + .../java/org/jline/terminal/impl/Diag.java | 11 + .../org/jline/terminal/impl/DumbTerminal.java | 1 + .../jline/terminal/impl/ExternalTerminal.java | 1 + .../terminal/impl/LineDisciplineTerminal.java | 1 + .../jline/terminal/impl/PosixPtyTerminal.java | 1 + .../jline/terminal/impl/PosixSysTerminal.java | 1 + .../jline/terminal/impl/jep442/CLibrary.java | 1069 +++++++++++++++++ .../impl/jep442/Jep442TerminalProvider.java | 96 ++ .../jline/terminal/impl/jep442/Kernel32.java | 843 +++++++++++++ .../jline/terminal/impl/jep442/NativePty.java | 147 +++ .../impl/jep442/NativeWinConsoleWriter.java | 32 + .../impl/jep442/NativeWinSysTerminal.java | 272 +++++ .../impl/jep442/WindowsAnsiWriter.java | 407 +++++++ .../java/org/jline/utils/ColorPalette.java | 1 + .../main/java/org/jline/utils/Display.java | 1 + .../src/main/java/org/jline/utils/Status.java | 1 + .../org/jline/terminal/provider/jep434 | 16 + 28 files changed, 2988 insertions(+), 2 deletions(-) create mode 100644 terminal/src/main/java/org/jline/terminal/impl/jep442/CLibrary.java create mode 100644 terminal/src/main/java/org/jline/terminal/impl/jep442/Jep442TerminalProvider.java create mode 100644 terminal/src/main/java/org/jline/terminal/impl/jep442/Kernel32.java create mode 100644 terminal/src/main/java/org/jline/terminal/impl/jep442/NativePty.java create mode 100644 terminal/src/main/java/org/jline/terminal/impl/jep442/NativeWinConsoleWriter.java create mode 100644 terminal/src/main/java/org/jline/terminal/impl/jep442/NativeWinSysTerminal.java create mode 100644 terminal/src/main/java/org/jline/terminal/impl/jep442/WindowsAnsiWriter.java create mode 100644 terminal/src/main/resources/META-INF/services/org/jline/terminal/provider/jep434 diff --git a/jline/pom.xml b/jline/pom.xml index b2eedb4f8..d933d5a69 100644 --- a/jline/pom.xml +++ b/jline/pom.xml @@ -320,6 +320,9 @@ **/TTop.java **/ConsoleEngineImpl.java + + **/jep434/*.java + -Xlint:all,-options -Werror @@ -335,6 +338,7 @@ **/TTop.java **/ConsoleEngineImpl.java + **/jep434/*.java -Xlint:all,-options @@ -344,6 +348,22 @@ + + jdk20 + + compile + + + + **/jep434/*.java + + 21 + + -Xlint:all,-options + --enable-preview + + + diff --git a/reader/pom.xml b/reader/pom.xml index 278bcb6e9..2a7a309c4 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/main/java/org/jline/reader/impl/LineReaderImpl.java b/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java index 9b8a72188..57074f724 100644 --- a/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java +++ b/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java @@ -232,7 +232,7 @@ protected enum BellType { protected KillRing killRing = new KillRing(); - protected UndoTree undo = new UndoTree<>(this::setBuffer); + protected UndoTree undo; protected boolean isUndo; /** @@ -291,6 +291,7 @@ public LineReaderImpl(Terminal terminal, String appName) throws IOException { this(terminal, appName, null); } + @SuppressWarnings("this-escape") public LineReaderImpl(Terminal terminal, String appName, Map variables) { Objects.requireNonNull(terminal, "terminal can not be null"); this.terminal = terminal; @@ -309,6 +310,7 @@ public LineReaderImpl(Terminal terminal, String appName, Map var this.alternateOut = Curses.tputs(terminal.getStringCapability(Capability.exit_alt_charset_mode)); } + undo = new UndoTree<>(this::setBuffer); builtinWidgets = builtinWidgets(); widgets = new HashMap<>(builtinWidgets); bindingReader = new BindingReader(terminal.reader()); diff --git a/reader/src/main/java/org/jline/reader/impl/UndoTree.java b/reader/src/main/java/org/jline/reader/impl/UndoTree.java index 0303fdff5..3c9736e07 100644 --- a/reader/src/main/java/org/jline/reader/impl/UndoTree.java +++ b/reader/src/main/java/org/jline/reader/impl/UndoTree.java @@ -20,6 +20,7 @@ public class UndoTree { private final Node parent; private Node current; + @SuppressWarnings("this-escape") public UndoTree(Consumer s) { state = s; parent = new Node(null); diff --git a/reader/src/main/java/org/jline/reader/impl/history/DefaultHistory.java b/reader/src/main/java/org/jline/reader/impl/history/DefaultHistory.java index cbb509ca2..416c3039d 100644 --- a/reader/src/main/java/org/jline/reader/impl/history/DefaultHistory.java +++ b/reader/src/main/java/org/jline/reader/impl/history/DefaultHistory.java @@ -43,6 +43,7 @@ public class DefaultHistory implements History { public DefaultHistory() {} + @SuppressWarnings("this-escape") public DefaultHistory(LineReader reader) { attach(reader); } diff --git a/terminal/pom.xml b/terminal/pom.xml index ed3a818c2..5b957f0a6 100644 --- a/terminal/pom.xml +++ b/terminal/pom.xml @@ -47,6 +47,41 @@ + + org.apache.maven.plugins + maven-compiler-plugin + 3.10.1 + + + default-compile + + + **/jep442/*.java + + + -Xlint:all,-options + -Werror + + + + + jdk21 + + compile + + + + **/jep442/*.java + + 21 + + -Xlint:all,-options + --enable-preview + + + + + org.apache.felix maven-bundle-plugin diff --git a/terminal/src/main/java/org/jline/terminal/Attributes.java b/terminal/src/main/java/org/jline/terminal/Attributes.java index 07b7ff471..a33977fa4 100644 --- a/terminal/src/main/java/org/jline/terminal/Attributes.java +++ b/terminal/src/main/java/org/jline/terminal/Attributes.java @@ -137,6 +137,7 @@ public enum LocalFlag { public Attributes() {} + @SuppressWarnings("this-escape") public Attributes(Attributes attr) { copy(attr); } diff --git a/terminal/src/main/java/org/jline/terminal/Size.java b/terminal/src/main/java/org/jline/terminal/Size.java index 3279d77d6..f5ccd199c 100644 --- a/terminal/src/main/java/org/jline/terminal/Size.java +++ b/terminal/src/main/java/org/jline/terminal/Size.java @@ -15,6 +15,7 @@ public class Size { public Size() {} + @SuppressWarnings("this-escape") public Size(int columns, int rows) { this(); setColumns(columns); diff --git a/terminal/src/main/java/org/jline/terminal/TerminalBuilder.java b/terminal/src/main/java/org/jline/terminal/TerminalBuilder.java index dc57b1802..688dbb01c 100644 --- a/terminal/src/main/java/org/jline/terminal/TerminalBuilder.java +++ b/terminal/src/main/java/org/jline/terminal/TerminalBuilder.java @@ -54,6 +54,7 @@ public final class TerminalBuilder { 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_JEP434 = "org.jline.terminal.jep434"; 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"; @@ -129,6 +130,7 @@ public static TerminalBuilder builder() { private Boolean jna; private Boolean jansi; private Boolean exec; + private Boolean jep434; private Boolean dumb; private Boolean color; private Attributes attributes; @@ -184,6 +186,11 @@ public TerminalBuilder exec(boolean exec) { return this; } + public TerminalBuilder jep434(boolean jep434) { + this.jep434 = jep434; + return this; + } + public TerminalBuilder dumb(boolean dumb) { this.dumb = dumb; return this; @@ -368,12 +375,25 @@ private Terminal doBuild() throws IOException { if (exec == null) { exec = getBoolean(PROP_EXEC, true); } + Boolean jep434 = this.jep434; + if (jep434 == null) { + jep434 = getBoolean(PROP_JEP434, 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 (jep434) { + try { + TerminalProvider provider = TerminalProvider.load("jep434"); + providers.add(provider); + } catch (Throwable t) { + Log.debug("Unable to load JEP434 support: ", t); + exception.addSuppressed(t); + } + } if (jna) { try { TerminalProvider provider = TerminalProvider.load("jna"); diff --git a/terminal/src/main/java/org/jline/terminal/impl/AbstractTerminal.java b/terminal/src/main/java/org/jline/terminal/impl/AbstractTerminal.java index d5a806c84..36dde577a 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/AbstractTerminal.java +++ b/terminal/src/main/java/org/jline/terminal/impl/AbstractTerminal.java @@ -44,7 +44,7 @@ public abstract class AbstractTerminal implements Terminal { protected final Set bools = new HashSet<>(); protected final Map ints = new HashMap<>(); protected final Map strings = new HashMap<>(); - protected final ColorPalette palette = new ColorPalette(this); + protected final ColorPalette palette; protected Status status; protected Runnable onClose; @@ -52,11 +52,13 @@ public AbstractTerminal(String name, String type) throws IOException { this(name, type, null, SignalHandler.SIG_DFL); } + @SuppressWarnings("this-escape") public AbstractTerminal(String name, String type, Charset encoding, SignalHandler signalHandler) throws IOException { this.name = name; this.type = type != null ? type : "ansi"; this.encoding = encoding != null ? encoding : Charset.defaultCharset(); + this.palette = new ColorPalette(this); for (Signal signal : Signal.values()) { handlers.put(signal, signalHandler); } diff --git a/terminal/src/main/java/org/jline/terminal/impl/AbstractWindowsTerminal.java b/terminal/src/main/java/org/jline/terminal/impl/AbstractWindowsTerminal.java index b90d9628f..bd5d77740 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/AbstractWindowsTerminal.java +++ b/terminal/src/main/java/org/jline/terminal/impl/AbstractWindowsTerminal.java @@ -82,6 +82,7 @@ public abstract class AbstractWindowsTerminal extends AbstractTerminal protected boolean focusTracking = false; private volatile boolean closing; + @SuppressWarnings("this-escape") public AbstractWindowsTerminal( Writer writer, String name, 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..3bed9dc0a 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(); + // Jep434 + out.println("Jep434 Support"); + out.println("================="); + try { + TerminalProvider provider = TerminalProvider.load("jep434"); + testProvider(out, provider); + } catch (Throwable t) { + out.println("Jep434 support not available: " + t); + } + out.println(); + out.println("JnaSupport"); out.println("================="); try { diff --git a/terminal/src/main/java/org/jline/terminal/impl/DumbTerminal.java b/terminal/src/main/java/org/jline/terminal/impl/DumbTerminal.java index 5c86095ec..a6006ffe2 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/DumbTerminal.java +++ b/terminal/src/main/java/org/jline/terminal/impl/DumbTerminal.java @@ -40,6 +40,7 @@ public DumbTerminal(String name, String type, InputStream in, OutputStream out, this(name, type, in, out, encoding, SignalHandler.SIG_DFL); } + @SuppressWarnings("this-escape") public DumbTerminal( String name, String type, InputStream in, OutputStream out, Charset encoding, SignalHandler signalHandler) throws IOException { diff --git a/terminal/src/main/java/org/jline/terminal/impl/ExternalTerminal.java b/terminal/src/main/java/org/jline/terminal/impl/ExternalTerminal.java index d463794da..39fa27307 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/ExternalTerminal.java +++ b/terminal/src/main/java/org/jline/terminal/impl/ExternalTerminal.java @@ -67,6 +67,7 @@ public ExternalTerminal( this(name, type, masterInput, masterOutput, encoding, signalHandler, paused, null, null); } + @SuppressWarnings("this-escape") public ExternalTerminal( String name, String type, diff --git a/terminal/src/main/java/org/jline/terminal/impl/LineDisciplineTerminal.java b/terminal/src/main/java/org/jline/terminal/impl/LineDisciplineTerminal.java index 3a31715af..a5576140a 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/LineDisciplineTerminal.java +++ b/terminal/src/main/java/org/jline/terminal/impl/LineDisciplineTerminal.java @@ -91,6 +91,7 @@ public LineDisciplineTerminal(String name, String type, OutputStream masterOutpu this(name, type, masterOutput, encoding, SignalHandler.SIG_DFL); } + @SuppressWarnings("this-escape") public LineDisciplineTerminal( String name, String type, OutputStream masterOutput, Charset encoding, SignalHandler signalHandler) throws IOException { diff --git a/terminal/src/main/java/org/jline/terminal/impl/PosixPtyTerminal.java b/terminal/src/main/java/org/jline/terminal/impl/PosixPtyTerminal.java index 6047f2c19..db3913a99 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/PosixPtyTerminal.java +++ b/terminal/src/main/java/org/jline/terminal/impl/PosixPtyTerminal.java @@ -55,6 +55,7 @@ public PosixPtyTerminal( this(name, type, pty, in, out, encoding, signalHandler, false); } + @SuppressWarnings("this-escape") public PosixPtyTerminal( String name, String type, diff --git a/terminal/src/main/java/org/jline/terminal/impl/PosixSysTerminal.java b/terminal/src/main/java/org/jline/terminal/impl/PosixSysTerminal.java index c07b6d6b2..e6fbacdf2 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/PosixSysTerminal.java +++ b/terminal/src/main/java/org/jline/terminal/impl/PosixSysTerminal.java @@ -34,6 +34,7 @@ public class PosixSysTerminal extends AbstractPosixTerminal { protected final Map nativeHandlers = new HashMap<>(); protected final Task closer; + @SuppressWarnings("this-escape") public PosixSysTerminal( String name, String type, Pty pty, Charset encoding, boolean nativeSignals, SignalHandler signalHandler) throws IOException { diff --git a/terminal/src/main/java/org/jline/terminal/impl/jep442/CLibrary.java b/terminal/src/main/java/org/jline/terminal/impl/jep442/CLibrary.java new file mode 100644 index 000000000..866cae775 --- /dev/null +++ b/terminal/src/main/java/org/jline/terminal/impl/jep442/CLibrary.java @@ -0,0 +1,1069 @@ +/* + * 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.jep442; + +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; + +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 MemorySegment seg; + + winsize() { + seg = Arena.ofAuto().allocate(LAYOUT); + } + + winsize(short ws_col, short ws_row) { + this(); + ws_col(ws_col); + ws_row(ws_row); + } + + 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 MemorySegment seg; + + termios() { + seg = 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(MemorySegment.ofArray(c_cc)); + } + + MemorySegment address() { + 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); + } + + 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)); + // 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.address()); + 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.address()); + } 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 { + MemorySegment buf = 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 { + winsize ws = new winsize(); + termios t = new termios(); + + int[] master = new int[1]; + int[] slave = new int[1]; + byte[] buf = new byte[64]; + int res = (int) openpty.invoke( + master, + slave, + buf, + attr != null ? new termios(attr) : null, + size != null ? new winsize((short) size.getRows(), (short) size.getColumns()) : null); + int len = 0; + while (buf[len] != 0) { + len++; + } + String device = new String(buf, 0, len); + return new NativePty(master[0], slave[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/src/main/java/org/jline/terminal/impl/jep442/Jep442TerminalProvider.java b/terminal/src/main/java/org/jline/terminal/impl/jep442/Jep442TerminalProvider.java new file mode 100644 index 000000000..f2fb43271 --- /dev/null +++ b/terminal/src/main/java/org/jline/terminal/impl/jep442/Jep442TerminalProvider.java @@ -0,0 +1,96 @@ +/* + * 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.jep442; + +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 Jep442TerminalProvider implements TerminalProvider { + @Override + public String name() { + return "jep442"; + } + + @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 NativePty( + -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 NativePty.isPosixSystemStream(stream); + } + + @Override + public String systemStreamName(Stream stream) { + return NativePty.posixSystemStreamName(stream); + } +} diff --git a/terminal/src/main/java/org/jline/terminal/impl/jep442/Kernel32.java b/terminal/src/main/java/org/jline/terminal/impl/jep442/Kernel32.java new file mode 100644 index 000000000..9a27fb992 --- /dev/null +++ b/terminal/src/main/java/org/jline/terminal/impl/jep442/Kernel32.java @@ -0,0 +1,843 @@ +/* + * 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.jep442; + +import java.io.IOException; +import java.lang.foreign.*; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.VarHandle; +import java.util.Objects; + +import static java.lang.foreign.ValueLayout.JAVA_INT; +import static java.lang.foreign.ValueLayout.OfBoolean; +import static java.lang.foreign.ValueLayout.OfByte; +import static java.lang.foreign.ValueLayout.OfChar; +import static java.lang.foreign.ValueLayout.OfDouble; +import static java.lang.foreign.ValueLayout.OfFloat; +import static java.lang.foreign.ValueLayout.OfInt; +import static java.lang.foreign.ValueLayout.OfLong; +import static java.lang.foreign.ValueLayout.OfShort; + +@SuppressWarnings({"unused", "CopyConstructorMissesField"}) +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(MemorySegment hHandle, int dwMilliseconds) { + var 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 MemorySegment GetStdHandle(int nStdHandle) { + var mh$ = requireNonNull(GetStdHandle$MH, "GetStdHandle"); + try { + return MemorySegment.ofAddress((long) mh$.invokeExact(nStdHandle)); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + public static int FormatMessageW( + int dwFlags, + MemorySegment lpSource, + int dwMessageId, + int dwLanguageId, + MemorySegment lpBuffer, + int nSize, + MemorySegment Arguments) { + var mh$ = requireNonNull(FormatMessageW$MH, "FormatMessageW"); + try { + return (int) mh$.invokeExact( + dwFlags, + lpSource.address(), + dwMessageId, + dwLanguageId, + lpBuffer.address(), + nSize, + Arguments.address()); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + public static int SetConsoleTextAttribute(MemorySegment hConsoleOutput, short wAttributes) { + var 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(MemorySegment hConsoleHandle, int dwMode) { + var mh$ = requireNonNull(SetConsoleMode$MH, "SetConsoleMode"); + try { + return (int) mh$.invokeExact(hConsoleHandle.address(), dwMode); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + public static int GetConsoleMode(MemorySegment hConsoleHandle, MemorySegment lpMode) { + var mh$ = requireNonNull(GetConsoleMode$MH, "GetConsoleMode"); + try { + return (int) mh$.invokeExact(hConsoleHandle.address(), lpMode.address()); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + public static int SetConsoleTitleW(MemorySegment lpConsoleTitle) { + var mh$ = requireNonNull(SetConsoleTitleW$MH, "SetConsoleTitleW"); + try { + return (int) mh$.invokeExact(lpConsoleTitle.address()); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + public static int SetConsoleCursorPosition(MemorySegment hConsoleOutput, COORD dwCursorPosition) { + var 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( + MemorySegment hConsoleOutput, + char cCharacter, + int nLength, + COORD dwWriteCoord, + MemorySegment lpNumberOfCharsWritten) { + var mh$ = requireNonNull(FillConsoleOutputCharacterW$MH, "FillConsoleOutputCharacterW"); + try { + return (int) mh$.invokeExact( + hConsoleOutput.address(), cCharacter, nLength, dwWriteCoord.seg, lpNumberOfCharsWritten.address()); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + public static int FillConsoleOutputAttribute( + MemorySegment hConsoleOutput, + short wAttribute, + int nLength, + COORD dwWriteCoord, + MemorySegment lpNumberOfAttrsWritten) { + var mh$ = requireNonNull(FillConsoleOutputAttribute$MH, "FillConsoleOutputAttribute"); + try { + return (int) mh$.invokeExact( + hConsoleOutput, wAttribute, nLength, dwWriteCoord.seg, lpNumberOfAttrsWritten.address()); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + public static int WriteConsoleW( + MemorySegment hConsoleOutput, + MemorySegment lpBuffer, + int nNumberOfCharsToWrite, + MemorySegment lpNumberOfCharsWritten, + MemorySegment lpReserved) { + var 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( + MemorySegment hConsoleInput, MemorySegment lpBuffer, int nLength, MemorySegment lpNumberOfEventsRead) { + var mh$ = requireNonNull(ReadConsoleInputW$MH, "ReadConsoleInputW"); + try { + return (int) mh$.invokeExact( + hConsoleInput.address(), lpBuffer.address(), nLength, lpNumberOfEventsRead.address()); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + public static int PeekConsoleInputW( + MemorySegment hConsoleInput, MemorySegment lpBuffer, int nLength, MemorySegment lpNumberOfEventsRead) { + var mh$ = requireNonNull(PeekConsoleInputW$MH, "PeekConsoleInputW"); + try { + return (int) mh$.invokeExact( + hConsoleInput.address(), lpBuffer.address(), nLength, lpNumberOfEventsRead.address()); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + public static int GetConsoleScreenBufferInfo( + MemorySegment hConsoleOutput, CONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo) { + var mh$ = requireNonNull(GetConsoleScreenBufferInfo$MH, "GetConsoleScreenBufferInfo"); + try { + return (int) mh$.invokeExact(hConsoleOutput.address(), lpConsoleScreenBufferInfo.seg); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + public static int ScrollConsoleScreenBuffer( + MemorySegment hConsoleOutput, + SMALL_RECT lpScrollRectangle, + SMALL_RECT lpClipRectangle, + COORD dwDestinationOrigin, + CHAR_INFO lpFill) { + var mh$ = requireNonNull(ScrollConsoleScreenBuffer$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(Object... x0) { + var mh$ = requireNonNull(GetLastError$MH, "GetLastError"); + try { + return (int) mh$.invokeExact(x0); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + public static INPUT_RECORD[] readConsoleInputHelper(MemorySegment handle, int count, boolean peek) + throws IOException { + try (Arena session = Arena.ofConfined()) { + MemorySegment inputRecordPtr = session.allocateArray(INPUT_RECORD.LAYOUT, count); + MemorySegment length = session.allocate(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_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; + MemorySegment data = Arena.ofAuto().allocate(bufferSize); + FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, null, errorCode, 0, data, bufferSize, null); + return data.getUtf8String(0).trim(); + } + + static final OfBoolean C_BOOL$LAYOUT = ValueLayout.JAVA_BOOLEAN; + static final OfByte C_CHAR$LAYOUT = ValueLayout.JAVA_BYTE; + static final OfChar C_WCHAR$LAYOUT = ValueLayout.JAVA_CHAR.withByteAlignment(16); + static final OfShort C_SHORT$LAYOUT = ValueLayout.JAVA_SHORT.withByteAlignment(16); + static final OfShort C_WORD$LAYOUT = ValueLayout.JAVA_SHORT.withByteAlignment(16); + static final OfInt C_DWORD$LAYOUT = ValueLayout.JAVA_INT.withByteAlignment(32); + static final OfInt C_INT$LAYOUT = JAVA_INT.withByteAlignment(32); + static final OfLong C_LONG$LAYOUT = ValueLayout.JAVA_LONG.withByteAlignment(64); + static final OfLong C_LONG_LONG$LAYOUT = ValueLayout.JAVA_LONG.withByteAlignment(64); + static final OfFloat C_FLOAT$LAYOUT = ValueLayout.JAVA_FLOAT.withByteAlignment(32); + static final OfDouble C_DOUBLE$LAYOUT = ValueLayout.JAVA_DOUBLE.withByteAlignment(64); + static final AddressLayout C_POINTER$LAYOUT = ValueLayout.ADDRESS.withByteAlignment(64); + + static final MethodHandle WaitForSingleObject$MH = + downcallHandle("WaitForSingleObject", FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, C_INT$LAYOUT)); + static final MethodHandle GetStdHandle$MH = + downcallHandle("GetStdHandle", FunctionDescriptor.of(C_POINTER$LAYOUT, C_INT$LAYOUT)); + static final MethodHandle FormatMessageW$MH = downcallHandle( + "FormatMessageW", + 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", FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, C_SHORT$LAYOUT)); + static final MethodHandle SetConsoleMode$MH = + downcallHandle("SetConsoleMode", FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, C_INT$LAYOUT)); + static final MethodHandle GetConsoleMode$MH = + downcallHandle("GetConsoleMode", FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, C_POINTER$LAYOUT)); + + static final MethodHandle SetConsoleTitleW$MH = + downcallHandle("SetConsoleTitleW", FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT)); + static final MethodHandle SetConsoleCursorPosition$MH = downcallHandle( + "SetConsoleCursorPosition", FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, COORD.LAYOUT)); + static final MethodHandle FillConsoleOutputCharacterW$MH = downcallHandle( + "FillConsoleOutputCharacterW", + FunctionDescriptor.of( + C_INT$LAYOUT, C_POINTER$LAYOUT, C_SHORT$LAYOUT, C_INT$LAYOUT, COORD.LAYOUT, C_POINTER$LAYOUT)); + static final MethodHandle FillConsoleOutputAttribute$MH = downcallHandle( + "FillConsoleOutputAttribute", + 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", + 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", + FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, C_POINTER$LAYOUT, C_INT$LAYOUT, C_POINTER$LAYOUT)); + static final MethodHandle PeekConsoleInputW$MH = downcallHandle( + "PeekConsoleInputW", + FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, C_POINTER$LAYOUT, C_INT$LAYOUT, C_POINTER$LAYOUT)); + + static final MethodHandle GetConsoleScreenBufferInfo$MH = downcallHandle( + "GetConsoleScreenBufferInfo", FunctionDescriptor.of(C_INT$LAYOUT, C_POINTER$LAYOUT, C_POINTER$LAYOUT)); + + static final MethodHandle ScrollConsoleScreenBuffer$MH = downcallHandle( + "ScrollConsoleScreenBuffer", + 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", FunctionDescriptor.of(C_INT$LAYOUT)); + + public static class INPUT_RECORD { + static final MemoryLayout LAYOUT = MemoryLayout.structLayout( + ValueLayout.JAVA_SHORT.withName("EventType"), + 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 MemorySegment seg; + + public INPUT_RECORD() { + this(Arena.ofAuto().allocate(LAYOUT)); + } + + INPUT_RECORD(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 class MENU_EVENT_RECORD { + + static final GroupLayout LAYOUT = MemoryLayout.structLayout(C_DWORD$LAYOUT.withName("dwCommandId")); + static final VarHandle COMMAND_ID = varHandle(LAYOUT, "dwCommandId"); + + private final MemorySegment seg; + + public MENU_EVENT_RECORD() { + this(Arena.ofAuto().allocate(LAYOUT)); + } + + MENU_EVENT_RECORD(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 class FOCUS_EVENT_RECORD { + + static final GroupLayout LAYOUT = MemoryLayout.structLayout(C_BOOL$LAYOUT.withName("bSetFocus")); + static final VarHandle SET_FOCUS = varHandle(LAYOUT, "bSetFocus"); + + private final MemorySegment seg; + + public FOCUS_EVENT_RECORD() { + this(Arena.ofAuto().allocate(LAYOUT)); + } + + FOCUS_EVENT_RECORD(MemorySegment seg) { + this.seg = Objects.requireNonNull(seg); + } + + FOCUS_EVENT_RECORD(MemorySegment seg, long offset) { + this.seg = Objects.requireNonNull(seg).asSlice(offset, LAYOUT.byteSize()); + } + + public boolean setFocus() { + return (boolean) FOCUS_EVENT_RECORD.SET_FOCUS.get(seg); + } + + public void setFocus(boolean setFocus) { + FOCUS_EVENT_RECORD.SET_FOCUS.set(seg, setFocus); + } + } + + public static class WINDOW_BUFFER_SIZE_RECORD { + + static final GroupLayout LAYOUT = MemoryLayout.structLayout(COORD.LAYOUT.withName("size")); + static final long SIZE_OFFSET = byteOffset(LAYOUT, "size"); + + private final MemorySegment seg; + + public WINDOW_BUFFER_SIZE_RECORD() { + this(Arena.ofAuto().allocate(LAYOUT)); + } + + WINDOW_BUFFER_SIZE_RECORD(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 class MOUSE_EVENT_RECORD { + + private static final MemoryLayout LAYOUT = MemoryLayout.structLayout( + COORD.LAYOUT.withName("dwMousePosition"), + C_DWORD$LAYOUT.withName("dwButtonState"), + C_DWORD$LAYOUT.withName("dwControlKeyState"), + C_DWORD$LAYOUT.withName("dwEventFlags")); + private static final long MOUSE_POSITION_OFFSET = byteOffset(LAYOUT, "dwMousePosition"); + private static final VarHandle BUTTON_STATE = varHandle(LAYOUT, "dwButtonState"); + private static final VarHandle CONTROL_KEY_STATE = varHandle(LAYOUT, "dwControlKeyState"); + private static final VarHandle EVENT_FLAGS = varHandle(LAYOUT, "dwEventFlags"); + + private final MemorySegment seg; + + public MOUSE_EVENT_RECORD() { + this(Arena.ofAuto().allocate(LAYOUT)); + } + + MOUSE_EVENT_RECORD(MemorySegment seg) { + this.seg = Objects.requireNonNull(seg); + } + + MOUSE_EVENT_RECORD(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 class KEY_EVENT_RECORD { + + static final MemoryLayout LAYOUT = MemoryLayout.structLayout( + JAVA_INT.withName("bKeyDown"), + ValueLayout.JAVA_SHORT.withName("wRepeatCount"), + ValueLayout.JAVA_SHORT.withName("wVirtualKeyCode"), + ValueLayout.JAVA_SHORT.withName("wVirtualScanCode"), + MemoryLayout.unionLayout( + ValueLayout.JAVA_CHAR.withName("UnicodeChar"), + ValueLayout.JAVA_BYTE.withName("AsciiChar")) + .withName("uChar"), + 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 MemorySegment seg; + + public KEY_EVENT_RECORD() { + this(Arena.ofAuto().allocate(LAYOUT)); + } + + KEY_EVENT_RECORD(MemorySegment seg) { + this.seg = seg; + } + + KEY_EVENT_RECORD(MemorySegment seg, long offset) { + this.seg = Objects.requireNonNull(seg).asSlice(offset, LAYOUT.byteSize()); + } + + public boolean keyDown() { + return (boolean) bKeyDown$VH.get(seg); + } + + 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 class CHAR_INFO { + + static final GroupLayout LAYOUT = MemoryLayout.structLayout( + 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 MemorySegment seg; + + public CHAR_INFO() { + this(Arena.ofAuto().allocate(LAYOUT)); + } + + public CHAR_INFO(char c, short a) { + this(); + UnicodeChar$VH.set(seg, c); + Attributes$VH.set(seg, a); + } + + CHAR_INFO(MemorySegment seg) { + this.seg = seg; + } + + public char unicodeChar() { + return (char) UnicodeChar$VH.get(seg); + } + } + + public static class CONSOLE_SCREEN_BUFFER_INFO { + static final GroupLayout LAYOUT = 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 MemorySegment seg; + + public CONSOLE_SCREEN_BUFFER_INFO() { + this(Arena.ofAuto().allocate(LAYOUT)); + } + + CONSOLE_SCREEN_BUFFER_INFO(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 class COORD { + + static final GroupLayout LAYOUT = + 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 MemorySegment seg; + + public COORD() { + this(Arena.ofAuto().allocate(LAYOUT)); + } + + public COORD(short x, short y) { + this(Arena.ofAuto().allocate(LAYOUT)); + x(x); + y(y); + } + + public COORD(COORD from) { + this(Arena.ofAuto().allocate(LAYOUT).copyFrom(Objects.requireNonNull(from).seg)); + } + + COORD(MemorySegment seg) { + this.seg = seg; + } + + COORD(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() { + return new COORD(this); + } + } + + public static class SMALL_RECT { + + static final GroupLayout LAYOUT = 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 MemorySegment seg; + + public SMALL_RECT() { + this(Arena.ofAuto().allocate(LAYOUT)); + } + + public SMALL_RECT(SMALL_RECT from) { + this(Arena.ofAuto().allocate(LAYOUT).copyFrom(from.seg)); + } + + SMALL_RECT(MemorySegment seg, long offset) { + this(seg.asSlice(offset, LAYOUT.byteSize())); + } + + SMALL_RECT(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 SMALL_RECT copy() { + return new SMALL_RECT(this); + } + } + + private static final Linker LINKER = Linker.nativeLinker(); + + private static final SymbolLookup SYMBOL_LOOKUP; + + static { + SymbolLookup loaderLookup = SymbolLookup.loaderLookup(); + SYMBOL_LOOKUP = + name -> loaderLookup.find(name).or(() -> LINKER.defaultLookup().find(name)); + } + + static MethodHandle downcallHandle(String name, FunctionDescriptor fdesc) { + return SYMBOL_LOOKUP + .find(name) + .map(addr -> LINKER.downcallHandle(addr, fdesc)) + .orElse(null); + } + + static T requireNonNull(T obj, String symbolName) { + if (obj == null) { + throw new UnsatisfiedLinkError("unresolved symbol: " + symbolName); + } + return obj; + } + + static VarHandle varHandle(MemoryLayout layout, String e1) { + return layout.varHandle(MemoryLayout.PathElement.groupElement(e1)); + } + + static VarHandle varHandle(MemoryLayout layout, String e1, String e2) { + return layout.varHandle(MemoryLayout.PathElement.groupElement(e1), MemoryLayout.PathElement.groupElement(e2)); + } + + static long byteOffset(MemoryLayout layout, String e1) { + return layout.byteOffset(MemoryLayout.PathElement.groupElement(e1)); + } +} diff --git a/terminal/src/main/java/org/jline/terminal/impl/jep442/NativePty.java b/terminal/src/main/java/org/jline/terminal/impl/jep442/NativePty.java new file mode 100644 index 000000000..2c08c09f6 --- /dev/null +++ b/terminal/src/main/java/org/jline/terminal/impl/jep442/NativePty.java @@ -0,0 +1,147 @@ +/* + * 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.jep442; + +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 NativePty 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 NativePty(int master, int slave, String name) { + this(master, newDescriptor(master), slave, newDescriptor(slave), slave, newDescriptor(slave), name); + } + + public NativePty( + 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 "NativePty[" + getName() + "]"; + } + + public static boolean isPosixSystemStream(TerminalProvider.Stream stream) { + return switch (stream) { + case Input -> CLibrary.isTty(0); + case Output -> CLibrary.isTty(1); + case Error -> CLibrary.isTty(2); + }; + } + + public static String posixSystemStreamName(TerminalProvider.Stream stream) { + return switch (stream) { + case Input -> CLibrary.ttyName(0); + case Output -> CLibrary.ttyName(1); + case Error -> CLibrary.ttyName(2); + }; + } +} diff --git a/terminal/src/main/java/org/jline/terminal/impl/jep442/NativeWinConsoleWriter.java b/terminal/src/main/java/org/jline/terminal/impl/jep442/NativeWinConsoleWriter.java new file mode 100644 index 000000000..be37bb5f7 --- /dev/null +++ b/terminal/src/main/java/org/jline/terminal/impl/jep442/NativeWinConsoleWriter.java @@ -0,0 +1,32 @@ +/* + * 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.jep442; + +import java.io.IOException; +import java.lang.foreign.MemorySegment; + +import org.jline.terminal.impl.AbstractWindowsConsoleWriter; + +import static org.jline.terminal.impl.jep442.Kernel32.GetStdHandle; +import static org.jline.terminal.impl.jep442.Kernel32.STD_OUTPUT_HANDLE; +import static org.jline.terminal.impl.jep442.Kernel32.WriteConsoleW; +import static org.jline.terminal.impl.jep442.Kernel32.getLastErrorMessage; + +class NativeWinConsoleWriter extends AbstractWindowsConsoleWriter { + + private final MemorySegment console = GetStdHandle(STD_OUTPUT_HANDLE); + + @Override + protected void writeConsole(char[] text, int len) throws IOException { + MemorySegment txt = MemorySegment.ofArray(text); + if (WriteConsoleW(console, txt, len, null, null) == 0) { + throw new IOException("Failed to write to console: " + getLastErrorMessage()); + } + } +} diff --git a/terminal/src/main/java/org/jline/terminal/impl/jep442/NativeWinSysTerminal.java b/terminal/src/main/java/org/jline/terminal/impl/jep442/NativeWinSysTerminal.java new file mode 100644 index 000000000..87e1d0ba2 --- /dev/null +++ b/terminal/src/main/java/org/jline/terminal/impl/jep442/NativeWinSysTerminal.java @@ -0,0 +1,272 @@ +/* + * 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.jep442; + +import java.io.BufferedWriter; +import java.io.IOError; +import java.io.IOException; +import java.io.Writer; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +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 java.lang.foreign.ValueLayout.JAVA_INT; +import static org.jline.terminal.impl.jep442.Kernel32.*; +import static org.jline.terminal.impl.jep442.Kernel32.GetConsoleMode; +import static org.jline.terminal.impl.jep442.Kernel32.GetConsoleScreenBufferInfo; +import static org.jline.terminal.impl.jep442.Kernel32.GetStdHandle; +import static org.jline.terminal.impl.jep442.Kernel32.INPUT_RECORD; +import static org.jline.terminal.impl.jep442.Kernel32.INVALID_HANDLE_VALUE; +import static org.jline.terminal.impl.jep442.Kernel32.KEY_EVENT_RECORD; +import static org.jline.terminal.impl.jep442.Kernel32.MOUSE_EVENT_RECORD; +import static org.jline.terminal.impl.jep442.Kernel32.STD_ERROR_HANDLE; +import static org.jline.terminal.impl.jep442.Kernel32.STD_INPUT_HANDLE; +import static org.jline.terminal.impl.jep442.Kernel32.STD_OUTPUT_HANDLE; +import static org.jline.terminal.impl.jep442.Kernel32.SetConsoleMode; +import static org.jline.terminal.impl.jep442.Kernel32.WaitForSingleObject; +import static org.jline.terminal.impl.jep442.Kernel32.getLastErrorMessage; +import static org.jline.terminal.impl.jep442.Kernel32.readConsoleInputHelper; + +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 { + // Get input console mode + MemorySegment consoleIn = GetStdHandle(STD_INPUT_HANDLE); + MemorySegment inMode = allocateInt(); + if (GetConsoleMode(consoleIn, inMode) == 0) { + throw new IOException("Failed to get console mode: " + getLastErrorMessage()); + } + // Get output console and mode + MemorySegment console = switch (consoleStream) { + case Output -> GetStdHandle(STD_OUTPUT_HANDLE); + case Error -> GetStdHandle(STD_ERROR_HANDLE); + default -> throw new IllegalArgumentException("Unsupport stream for console: " + consoleStream); + }; + MemorySegment outMode = allocateInt(); + 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_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_INT, 0), + console, + outMode.get(JAVA_INT, 0)); + // Start input pump thread + if (!paused) { + terminal.resume(); + } + return terminal; + } + + private static boolean enableVtp(MemorySegment console, int m) { + return SetConsoleMode(console, m | AbstractWindowsTerminal.ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0; + } + + public static boolean isWindowsSystemStream(TerminalProvider.Stream stream) { + MemorySegment console; + MemorySegment mode = allocateInt(); + 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 MemorySegment allocateInt() { + return Arena.ofAuto().allocate(JAVA_INT); + } + + NativeWinSysTerminal( + Writer writer, + String name, + String type, + Charset encoding, + boolean nativeSignals, + SignalHandler signalHandler, + MemorySegment inConsole, + int inConsoleMode, + MemorySegment outConsole, + int outConsoleMode) + throws IOException { + super( + writer, + name, + type, + encoding, + nativeSignals, + signalHandler, + inConsole, + inConsoleMode, + outConsole, + outConsoleMode); + } + + @Override + protected int getConsoleMode(MemorySegment console) { + try (Arena session = Arena.ofConfined()) { + MemorySegment mode = session.allocate(JAVA_INT); + if (GetConsoleMode(console, mode) == 0) { + return -1; + } + return mode.get(JAVA_INT, 0); + } + } + + @Override + protected void setConsoleMode(MemorySegment console, int mode) { + SetConsoleMode(console, mode); + } + + public Size getSize() { + CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO(); + GetConsoleScreenBufferInfo(outConsole, info); + return new Size(info.windowWidth(), info.windowHeight()); + } + + @Override + public Size getBufferSize() { + CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO(); + GetConsoleScreenBufferInfo(outConsole, info); + return new Size(info.size().x(), info.size().y()); + } + + protected boolean processConsoleInput() throws IOException { + INPUT_RECORD[] events; + if (inConsole != null + && inConsole.address() != INVALID_HANDLE_VALUE + && WaitForSingleObject(inConsole, 100) == 0) { + events = readConsoleInputHelper(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) { + CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO(); + 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/src/main/java/org/jline/terminal/impl/jep442/WindowsAnsiWriter.java b/terminal/src/main/java/org/jline/terminal/impl/jep442/WindowsAnsiWriter.java new file mode 100644 index 000000000..affae73f2 --- /dev/null +++ b/terminal/src/main/java/org/jline/terminal/impl/jep442/WindowsAnsiWriter.java @@ -0,0 +1,407 @@ +/* + * 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.jep442; + +import java.io.IOException; +import java.io.Writer; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; + +import org.jline.utils.AnsiWriter; +import org.jline.utils.Colors; + +import static org.jline.terminal.impl.jep442.Kernel32.BACKGROUND_BLUE; +import static org.jline.terminal.impl.jep442.Kernel32.BACKGROUND_GREEN; +import static org.jline.terminal.impl.jep442.Kernel32.BACKGROUND_INTENSITY; +import static org.jline.terminal.impl.jep442.Kernel32.BACKGROUND_RED; +import static org.jline.terminal.impl.jep442.Kernel32.CHAR_INFO; +import static org.jline.terminal.impl.jep442.Kernel32.CONSOLE_SCREEN_BUFFER_INFO; +import static org.jline.terminal.impl.jep442.Kernel32.COORD; +import static org.jline.terminal.impl.jep442.Kernel32.FOREGROUND_BLUE; +import static org.jline.terminal.impl.jep442.Kernel32.FOREGROUND_GREEN; +import static org.jline.terminal.impl.jep442.Kernel32.FOREGROUND_INTENSITY; +import static org.jline.terminal.impl.jep442.Kernel32.FOREGROUND_RED; +import static org.jline.terminal.impl.jep442.Kernel32.FillConsoleOutputAttribute; +import static org.jline.terminal.impl.jep442.Kernel32.FillConsoleOutputCharacterW; +import static org.jline.terminal.impl.jep442.Kernel32.GetConsoleScreenBufferInfo; +import static org.jline.terminal.impl.jep442.Kernel32.GetStdHandle; +import static org.jline.terminal.impl.jep442.Kernel32.SMALL_RECT; +import static org.jline.terminal.impl.jep442.Kernel32.STD_OUTPUT_HANDLE; +import static org.jline.terminal.impl.jep442.Kernel32.ScrollConsoleScreenBuffer; +import static org.jline.terminal.impl.jep442.Kernel32.SetConsoleCursorPosition; +import static org.jline.terminal.impl.jep442.Kernel32.SetConsoleTextAttribute; +import static org.jline.terminal.impl.jep442.Kernel32.SetConsoleTitleW; +import static org.jline.terminal.impl.jep442.Kernel32.getLastErrorMessage; + +class WindowsAnsiWriter extends AnsiWriter { + + private static final 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(); + 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 (Arena session = Arena.ofConfined()) { + MemorySegment written = session.allocate(ValueLayout.JAVA_INT); + switch (eraseOption) { + case ERASE_SCREEN: + COORD topLeft = new COORD((short) 0, info.window().top()); + int screenLength = info.window().height() * info.size().x(); + FillConsoleOutputAttribute(console, originalColors, screenLength, topLeft, written); + FillConsoleOutputCharacterW(console, ' ', screenLength, topLeft, written); + break; + case ERASE_SCREEN_TO_BEGINING: + COORD topLeft2 = new COORD((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); + break; + 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); + break; + default: + break; + } + } + } + + @Override + protected void processEraseLine(int eraseOption) throws IOException { + getConsoleInfo(); + try (Arena session = Arena.ofConfined()) { + MemorySegment written = session.allocate(ValueLayout.JAVA_INT); + switch (eraseOption) { + case ERASE_LINE: + COORD leftColCurrRow = + new COORD((short) 0, info.cursorPosition().y()); + FillConsoleOutputAttribute( + console, originalColors, info.size().x(), leftColCurrRow, written); + FillConsoleOutputCharacterW(console, ' ', info.size().x(), leftColCurrRow, written); + break; + case ERASE_LINE_TO_BEGINING: + COORD leftColCurrRow2 = + new COORD((short) 0, info.cursorPosition().y()); + FillConsoleOutputAttribute( + console, originalColors, info.cursorPosition().x(), leftColCurrRow2, written); + FillConsoleOutputCharacterW( + console, ' ', info.cursorPosition().x(), leftColCurrRow2, written); + break; + 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); + break; + default: + break; + } + } + } + + 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) { + SMALL_RECT scroll = new SMALL_RECT(info.window()); + scroll.top((short) 0); + COORD org = new COORD(); + org.x((short) 0); + org.y((short) (-nb)); + CHAR_INFO info = new CHAR_INFO(' ', 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(); + break; + case ATTRIBUTE_INTENSITY_NORMAL: + bold = false; + applyAttribute(); + break; + + case ATTRIBUTE_UNDERLINE: + underline = true; + applyAttribute(); + break; + case ATTRIBUTE_UNDERLINE_OFF: + underline = false; + applyAttribute(); + break; + + case ATTRIBUTE_NEGATIVE_ON: + negative = true; + applyAttribute(); + break; + case ATTRIBUTE_NEGATIVE_OFF: + negative = false; + applyAttribute(); + break; + default: + break; + } + } + + @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 { + getConsoleInfo(); + SMALL_RECT scroll = info.window().copy(); + scroll.top(info.cursorPosition().y()); + COORD org = new COORD((short) 0, (short) (info.cursorPosition().y() + optionInt)); + CHAR_INFO info = new CHAR_INFO(' ', originalColors); + if (ScrollConsoleScreenBuffer(console, scroll, scroll, org, info) == 0) { + throw new IOException(getLastErrorMessage()); + } + } + + @Override + protected void processDeleteLine(int optionInt) throws IOException { + getConsoleInfo(); + SMALL_RECT scroll = info.window().copy(); + scroll.top(info.cursorPosition().y()); + COORD org = new COORD((short) 0, (short) (info.cursorPosition().y() - optionInt)); + CHAR_INFO info = new CHAR_INFO(' ', originalColors); + if (ScrollConsoleScreenBuffer(console, scroll, scroll, org, info) == 0) { + throw new IOException(getLastErrorMessage()); + } + } + + @Override + protected void processChangeWindowTitle(String title) { + try (Arena session = Arena.ofConfined()) { + MemorySegment str = session.allocateUtf8String(title); + SetConsoleTitleW(str); + } + } +} diff --git a/terminal/src/main/java/org/jline/utils/ColorPalette.java b/terminal/src/main/java/org/jline/utils/ColorPalette.java index 7471760ba..22f52dbff 100644 --- a/terminal/src/main/java/org/jline/utils/ColorPalette.java +++ b/terminal/src/main/java/org/jline/utils/ColorPalette.java @@ -42,6 +42,7 @@ public ColorPalette(Terminal terminal) throws IOException { this(terminal, null); } + @SuppressWarnings("this-escape") public ColorPalette(Terminal terminal, String distance) throws IOException { this.terminal = terminal; this.distanceName = distance; diff --git a/terminal/src/main/java/org/jline/utils/Display.java b/terminal/src/main/java/org/jline/utils/Display.java index c172b92a1..b8fb88342 100644 --- a/terminal/src/main/java/org/jline/utils/Display.java +++ b/terminal/src/main/java/org/jline/utils/Display.java @@ -41,6 +41,7 @@ public class Display { protected final boolean delayedWrapAtEol; protected final boolean cursorDownIsNewLine; + @SuppressWarnings("this-escape") public Display(Terminal terminal, boolean fullscreen) { this.terminal = terminal; this.fullScreen = fullscreen; diff --git a/terminal/src/main/java/org/jline/utils/Status.java b/terminal/src/main/java/org/jline/utils/Status.java index cc1f50e26..d301d3df2 100644 --- a/terminal/src/main/java/org/jline/utils/Status.java +++ b/terminal/src/main/java/org/jline/utils/Status.java @@ -39,6 +39,7 @@ public static Status getStatus(Terminal terminal, boolean create) { return terminal instanceof AbstractTerminal ? ((AbstractTerminal) terminal).getStatus(create) : null; } + @SuppressWarnings("this-escape") public Status(AbstractTerminal terminal) { this.terminal = Objects.requireNonNull(terminal, "terminal can not be null"); this.supported = terminal.getStringCapability(Capability.change_scroll_region) != null diff --git a/terminal/src/main/resources/META-INF/services/org/jline/terminal/provider/jep434 b/terminal/src/main/resources/META-INF/services/org/jline/terminal/provider/jep434 new file mode 100644 index 000000000..d4d1f30df --- /dev/null +++ b/terminal/src/main/resources/META-INF/services/org/jline/terminal/provider/jep434 @@ -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.jep442.Jep442TerminalProvider