diff --git a/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/CLibrary.java b/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/CLibrary.java
index d042fc01d..ee2396e22 100644
--- a/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/CLibrary.java
+++ b/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/CLibrary.java
@@ -8,19 +8,36 @@
*/
package org.jline.terminal.impl.ffm;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.VarHandle;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
import java.util.EnumMap;
import java.util.EnumSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.jline.terminal.Attributes;
import org.jline.terminal.Size;
import org.jline.terminal.spi.Pty;
import org.jline.terminal.spi.TerminalProvider;
+import org.jline.utils.OSUtils;
@SuppressWarnings("preview")
class CLibrary {
+
+ private static final Logger logger = Logger.getLogger("org.jline");
+
// Window sizes.
// @see IOCTL_TTY(2) man-page
static class winsize {
@@ -379,44 +396,126 @@ public Attributes asAttributes() {
static MethodHandle tcsetattr;
static MethodHandle tcgetattr;
static MethodHandle ttyname_r;
+ static LinkageError openptyError;
static {
// methods
Linker linker = Linker.nativeLinker();
+ SymbolLookup lookup = SymbolLookup.loaderLookup().or(linker.defaultLookup());
// https://man7.org/linux/man-pages/man2/ioctl.2.html
ioctl = linker.downcallHandle(
- linker.defaultLookup().find("ioctl").get(),
+ lookup.find("ioctl").get(),
FunctionDescriptor.of(
ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, ValueLayout.JAVA_LONG, ValueLayout.ADDRESS),
Linker.Option.firstVariadicArg(2));
// https://www.man7.org/linux/man-pages/man3/isatty.3.html
isatty = linker.downcallHandle(
- linker.defaultLookup().find("isatty").get(),
- FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT));
- // https://man7.org/linux/man-pages/man3/openpty.3.html
- openpty = linker.downcallHandle(
- linker.defaultLookup().find("openpty").get(),
- FunctionDescriptor.of(
- ValueLayout.JAVA_INT,
- ValueLayout.ADDRESS,
- ValueLayout.ADDRESS,
- ValueLayout.ADDRESS,
- ValueLayout.ADDRESS,
- ValueLayout.ADDRESS));
+ lookup.find("isatty").get(), FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT));
// https://man7.org/linux/man-pages/man3/tcsetattr.3p.html
tcsetattr = linker.downcallHandle(
- linker.defaultLookup().find("tcsetattr").get(),
+ lookup.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(),
+ lookup.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(),
+ lookup.find("ttyname_r").get(),
FunctionDescriptor.of(
ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG));
+ // https://man7.org/linux/man-pages/man3/openpty.3.html
+ LinkageError error = null;
+ Optional openPtyAddr = lookup.find("openpty");
+ if (openPtyAddr.isEmpty()) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Unable to find openpty native method in static libraries and unable to load the util library.");
+ List suppressed = new ArrayList<>();
+ try {
+ System.loadLibrary("util");
+ openPtyAddr = lookup.find("openpty");
+ } catch (Throwable t) {
+ suppressed.add(t);
+ }
+ if (openPtyAddr.isEmpty()) {
+ String libUtilPath = System.getProperty("org.jline.ffm.libutil");
+ if (libUtilPath != null && !libUtilPath.isEmpty()) {
+ try {
+ System.load(libUtilPath);
+ openPtyAddr = lookup.find("openpty");
+ } catch (Throwable t) {
+ suppressed.add(t);
+ }
+ }
+ }
+ if (openPtyAddr.isEmpty() && OSUtils.IS_LINUX) {
+ String hwName;
+ try {
+ Process p = Runtime.getRuntime().exec(new String[] {"uname", "-m"});
+ p.waitFor();
+ try (InputStream in = p.getInputStream()) {
+ hwName = readFully(in).trim();
+ Path libDir = Paths.get("/usr/lib", hwName + "-linux-gnu");
+ try (Stream stream = Files.list(libDir)) {
+ List libs = stream.filter(
+ l -> l.getFileName().toString().startsWith("libutil.so."))
+ .collect(Collectors.toList());
+ for (Path lib : libs) {
+ try {
+ System.load(lib.toString());
+ openPtyAddr = lookup.find("openpty");
+ if (openPtyAddr.isPresent()) {
+ break;
+ }
+ } catch (Throwable t) {
+ suppressed.add(t);
+ }
+ }
+ }
+ }
+ } catch (Throwable t) {
+ suppressed.add(t);
+ }
+ }
+ if (openPtyAddr.isEmpty()) {
+ for (Throwable t : suppressed) {
+ sb.append("\n\t- ").append(t.toString());
+ }
+ error = new LinkageError(sb.toString());
+ suppressed.forEach(error::addSuppressed);
+ if (logger.isLoggable(Level.FINE)) {
+ logger.log(Level.WARNING, error.getMessage(), error);
+ } else {
+ logger.log(Level.WARNING, error.getMessage());
+ }
+ }
+ }
+ if (openPtyAddr.isPresent()) {
+ openpty = linker.downcallHandle(
+ openPtyAddr.get(),
+ FunctionDescriptor.of(
+ ValueLayout.JAVA_INT,
+ ValueLayout.ADDRESS,
+ ValueLayout.ADDRESS,
+ ValueLayout.ADDRESS,
+ ValueLayout.ADDRESS,
+ ValueLayout.ADDRESS));
+ openptyError = null;
+ } else {
+ openpty = null;
+ openptyError = error;
+ }
+ }
+
+ private static String readFully(InputStream in) throws IOException {
+ int readLen = 0;
+ ByteArrayOutputStream b = new ByteArrayOutputStream();
+ byte[] buf = new byte[32];
+ while ((readLen = in.read(buf, 0, buf.length)) >= 0) {
+ b.write(buf, 0, readLen);
+ }
+ return b.toString();
}
static Size getTerminalSize(int fd) {
@@ -484,6 +583,9 @@ static String ttyName(int fd) {
}
static Pty openpty(TerminalProvider provider, Attributes attr, Size size) {
+ if (openptyError != null) {
+ throw openptyError;
+ }
try {
java.lang.foreign.MemorySegment buf =
java.lang.foreign.Arena.ofAuto().allocate(64);
diff --git a/terminal-jansi/src/test/java/org/jline/terminal/impl/jansi/JansiTerminalProviderTest.java b/terminal-jansi/src/test/java/org/jline/terminal/impl/jansi/JansiTerminalProviderTest.java
index c4258c9cc..a20547c18 100644
--- a/terminal-jansi/src/test/java/org/jline/terminal/impl/jansi/JansiTerminalProviderTest.java
+++ b/terminal-jansi/src/test/java/org/jline/terminal/impl/jansi/JansiTerminalProviderTest.java
@@ -8,9 +8,19 @@
*/
package org.jline.terminal.impl.jansi;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.nio.charset.Charset;
+
+import org.jline.terminal.Terminal;
+import org.jline.terminal.spi.SystemStream;
import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
public class JansiTerminalProviderTest {
@@ -19,4 +29,29 @@ public void testJansiVersion() {
assertEquals(2, JansiTerminalProvider.JANSI_MAJOR_VERSION);
assertEquals(4, JansiTerminalProvider.JANSI_MINOR_VERSION);
}
+
+ @Test
+ void testIsSystemStream() {
+ assertDoesNotThrow(() -> new JansiTerminalProvider().isSystemStream(SystemStream.Output));
+ }
+
+ @Test
+ void testNewTerminal() throws IOException {
+ PipedOutputStream pos = new PipedOutputStream();
+ PipedInputStream pis = new PipedInputStream(pos);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ Terminal terminal = new JansiTerminalProvider()
+ .newTerminal(
+ "name",
+ "xterm",
+ pis,
+ baos,
+ Charset.defaultCharset(),
+ Terminal.SignalHandler.SIG_DFL,
+ true,
+ null,
+ null);
+ assertNotNull(terminal);
+ }
}
diff --git a/terminal-jna/src/test/java/org/jline/terminal/impl/jna/JnaTerminalProviderTest.java b/terminal-jna/src/test/java/org/jline/terminal/impl/jna/JnaTerminalProviderTest.java
new file mode 100644
index 000000000..f10dd4c54
--- /dev/null
+++ b/terminal-jna/src/test/java/org/jline/terminal/impl/jna/JnaTerminalProviderTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2002-2021, 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.jna;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.nio.charset.Charset;
+
+import org.jline.terminal.Terminal;
+import org.jline.terminal.spi.SystemStream;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+public class JnaTerminalProviderTest {
+
+ @Test
+ void testIsSystemStream() {
+ assertDoesNotThrow(() -> new JnaTerminalProvider().isSystemStream(SystemStream.Output));
+ }
+
+ @Test
+ void testNewTerminal() throws IOException {
+ PipedOutputStream pos = new PipedOutputStream();
+ PipedInputStream pis = new PipedInputStream(pos);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ Terminal terminal = new JnaTerminalProvider()
+ .newTerminal(
+ "name",
+ "xterm",
+ pis,
+ baos,
+ Charset.defaultCharset(),
+ Terminal.SignalHandler.SIG_DFL,
+ true,
+ null,
+ null);
+ assertNotNull(terminal);
+ }
+}
diff --git a/terminal-jni/src/test/java/org/jline/terminal/impl/jni/JniTerminalProviderTest.java b/terminal-jni/src/test/java/org/jline/terminal/impl/jni/JniTerminalProviderTest.java
new file mode 100644
index 000000000..0e6f938c8
--- /dev/null
+++ b/terminal-jni/src/test/java/org/jline/terminal/impl/jni/JniTerminalProviderTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2002-2021, 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.jni;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.nio.charset.Charset;
+
+import org.jline.terminal.Terminal;
+import org.jline.terminal.spi.SystemStream;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+public class JniTerminalProviderTest {
+
+ @Test
+ void testIsSystemStream() {
+ assertDoesNotThrow(() -> new JniTerminalProvider().isSystemStream(SystemStream.Output));
+ }
+
+ @Test
+ void testNewTerminal() throws IOException {
+ PipedOutputStream pos = new PipedOutputStream();
+ PipedInputStream pis = new PipedInputStream(pos);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ Terminal terminal = new JniTerminalProvider()
+ .newTerminal(
+ "name",
+ "xterm",
+ pis,
+ baos,
+ Charset.defaultCharset(),
+ Terminal.SignalHandler.SIG_DFL,
+ true,
+ null,
+ null);
+ assertNotNull(terminal);
+ }
+}
diff --git a/terminal/src/main/java/org/jline/utils/OSUtils.java b/terminal/src/main/java/org/jline/utils/OSUtils.java
index d1f3ae436..1d9f52429 100644
--- a/terminal/src/main/java/org/jline/utils/OSUtils.java
+++ b/terminal/src/main/java/org/jline/utils/OSUtils.java
@@ -12,9 +12,18 @@
public class OSUtils {
+ public static final boolean IS_LINUX =
+ System.getProperty("os.name").toLowerCase().contains("linux");
+
public static final boolean IS_WINDOWS =
System.getProperty("os.name").toLowerCase().contains("win");
+ public static final boolean IS_OSX =
+ System.getProperty("os.name").toLowerCase().contains("mac");
+
+ public static final boolean IS_AIX =
+ System.getProperty("os.name").toLowerCase().contains("aix");
+
public static final boolean IS_CYGWIN =
IS_WINDOWS && System.getenv("PWD") != null && System.getenv("PWD").startsWith("/");
@@ -36,11 +45,6 @@ public class OSUtils {
public static final boolean IS_CONEMU = IS_WINDOWS && System.getenv("ConEmuPID") != null;
- public static final boolean IS_OSX =
- System.getProperty("os.name").toLowerCase().contains("mac");
-
- public static final boolean IS_AIX = System.getProperty("os.name").equals("AIX");
-
public static String TTY_COMMAND;
public static String STTY_COMMAND;
public static String STTY_F_OPTION;