diff --git a/src/main/java/org/fusesource/jansi/AnsiConsole.java b/src/main/java/org/fusesource/jansi/AnsiConsole.java index 6099a050..63b6a0b5 100644 --- a/src/main/java/org/fusesource/jansi/AnsiConsole.java +++ b/src/main/java/org/fusesource/jansi/AnsiConsole.java @@ -15,20 +15,13 @@ */ package org.fusesource.jansi; -import java.io.FileDescriptor; -import java.io.FileOutputStream; -import java.io.IOError; -import java.io.IOException; -import java.io.OutputStream; -import java.io.PrintStream; -import java.io.UnsupportedEncodingException; +import java.io.*; import java.nio.charset.Charset; import java.nio.charset.UnsupportedCharsetException; import java.util.Locale; import org.fusesource.jansi.internal.CLibrary; import org.fusesource.jansi.internal.CLibrary.WinSize; -import org.fusesource.jansi.internal.Kernel32.CONSOLE_SCREEN_BUFFER_INFO; import org.fusesource.jansi.internal.MingwSupport; import org.fusesource.jansi.io.AnsiOutputStream; import org.fusesource.jansi.io.AnsiProcessor; @@ -37,12 +30,7 @@ import static org.fusesource.jansi.internal.CLibrary.ioctl; import static org.fusesource.jansi.internal.CLibrary.isatty; -import static org.fusesource.jansi.internal.Kernel32.GetConsoleMode; -import static org.fusesource.jansi.internal.Kernel32.GetConsoleScreenBufferInfo; -import static org.fusesource.jansi.internal.Kernel32.GetStdHandle; -import static org.fusesource.jansi.internal.Kernel32.STD_ERROR_HANDLE; -import static org.fusesource.jansi.internal.Kernel32.STD_OUTPUT_HANDLE; -import static org.fusesource.jansi.internal.Kernel32.SetConsoleMode; +import static org.fusesource.jansi.internal.Kernel32.*; /** * Provides consistent access to an ANSI aware console PrintStream or an ANSI codes stripping PrintStream @@ -313,13 +301,7 @@ public int getTerminalWidth() { processor = null; type = AnsiType.Native; installer = uninstaller = null; - MingwSupport mingw = new MingwSupport(); - String name = mingw.getConsoleName(stdout); - if (name != null && !name.isEmpty()) { - width = () -> mingw.getTerminalWidth(name); - } else { - width = () -> -1; - } + width = () -> MingwSupport.getTerminalWidth().orElse(-1); } else { // On Windows, when no ANSI-capable terminal is used, we know the console does not natively interpret // ANSI diff --git a/src/main/java/org/fusesource/jansi/AnsiMain.java b/src/main/java/org/fusesource/jansi/AnsiMain.java index 176dcf6c..01fa8b4d 100644 --- a/src/main/java/org/fusesource/jansi/AnsiMain.java +++ b/src/main/java/org/fusesource/jansi/AnsiMain.java @@ -23,6 +23,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; +import java.util.Optional; import java.util.Properties; import org.fusesource.jansi.Ansi.Attribute; @@ -204,11 +205,10 @@ private static void diagnoseTty(boolean stderr) { int[] mode = new int[1]; isatty = Kernel32.GetConsoleMode(console, mode); if ((AnsiConsole.IS_CONEMU || AnsiConsole.IS_CYGWIN || AnsiConsole.IS_MSYSTEM) && isatty == 0) { - MingwSupport mingw = new MingwSupport(); - String name = mingw.getConsoleName(!stderr); - if (name != null && !name.isEmpty()) { + Optional terminalWidth = MingwSupport.getTerminalWidth(); + if (terminalWidth.isPresent()) { isatty = 1; - width = mingw.getTerminalWidth(name); + width = terminalWidth.get(); } else { isatty = 0; width = 0; @@ -232,7 +232,6 @@ private static void diagnoseTty(boolean stderr) { } private static void testAnsi(boolean stderr) { - @SuppressWarnings("resource") PrintStream s = stderr ? System.err : System.out; s.print("test on System." + (stderr ? "err" : "out") + ":"); for (Ansi.Color c : Ansi.Color.values()) { diff --git a/src/main/java/org/fusesource/jansi/internal/MingwSupport.java b/src/main/java/org/fusesource/jansi/internal/MingwSupport.java index be0c54a2..f0d22a87 100644 --- a/src/main/java/org/fusesource/jansi/internal/MingwSupport.java +++ b/src/main/java/org/fusesource/jansi/internal/MingwSupport.java @@ -15,123 +15,62 @@ */ package org.fusesource.jansi.internal; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileDescriptor; +import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Support for MINGW terminals. - * Those terminals do not use the underlying windows terminal and there's no CLibrary available - * in these environments. We have to rely on calling {@code stty.exe} and {@code tty.exe} to - * obtain the terminal name and width. + *

+ * Those terminals do not use the underlying Windows terminal and there's no C library available + * in these environments. We have to rely on calling {@code stty.exe} and {@code tty.exe} to obtain + * the terminal name and width. */ public class MingwSupport { - private final String sttyCommand; - private final String ttyCommand; - private final Pattern columnsPatterns; + private static final Pattern COLUMNS_PATTERNS = Pattern.compile("\\d+ (\\d+)"); - public MingwSupport() { - String tty = null; - String stty = null; - String path = System.getenv("PATH"); - if (path != null) { - String[] paths = path.split(File.pathSeparator); - for (String p : paths) { - File ttyFile = new File(p, "tty.exe"); - if (tty == null && ttyFile.canExecute()) { - tty = ttyFile.getAbsolutePath(); - } - File sttyFile = new File(p, "stty.exe"); - if (stty == null && sttyFile.canExecute()) { - stty = sttyFile.getAbsolutePath(); - } - } - } - if (tty == null) { - tty = "tty.exe"; - } - if (stty == null) { - stty = "stty.exe"; - } - ttyCommand = tty; - sttyCommand = stty; - // Compute patterns - columnsPatterns = Pattern.compile("\\b" + "columns" + "\\s+(\\d+)\\b"); - } - - public String getConsoleName(boolean stdout) { + public static Optional getTerminalWidth() { try { - Process p = new ProcessBuilder(ttyCommand) - .redirectInput(getRedirect(stdout ? FileDescriptor.out : FileDescriptor.err)) - .start(); - String result = waitAndCapture(p); - if (p.exitValue() == 0) { - return result.trim(); - } - } catch (Throwable t) { - if ("java.lang.reflect.InaccessibleObjectException" - .equals(t.getClass().getName())) { - System.err.println("MINGW support requires --add-opens java.base/java.lang=ALL-UNNAMED"); + Optional terminalName = getTerminalName(); + if (terminalName.isPresent()) { + Process sttyProcess = new ProcessBuilder("stty.exe", "-F", terminalName.get(), "size").start(); + CharSequence result = waitAndCapture(sttyProcess); + Matcher matcher = COLUMNS_PATTERNS.matcher(result); + if (matcher.find()) { + return Optional.of(Integer.valueOf(matcher.group(1))); + } } + } catch (Exception e) { // ignore } - return null; + return Optional.empty(); } - public int getTerminalWidth(String name) { - try { - Process p = new ProcessBuilder(sttyCommand, "-F", name, "-a").start(); - String result = waitAndCapture(p); - if (p.exitValue() != 0) { - throw new IOException("Error executing '" + sttyCommand + "': " + result); - } - Matcher matcher = columnsPatterns.matcher(result); - if (matcher.find()) { - return Integer.parseInt(matcher.group(1)); - } - throw new IOException("Unable to parse columns"); - } catch (Exception e) { - throw new RuntimeException(e); + private static Optional getTerminalName() throws IOException, InterruptedException { + Process ttyProcess = new ProcessBuilder("tty.exe") + .redirectInput(ProcessBuilder.Redirect.INHERIT) + .start(); + CharSequence result = waitAndCapture(ttyProcess); + if (ttyProcess.exitValue() == 0) { + return Optional.of(result.toString()); } + return Optional.empty(); } - private static String waitAndCapture(Process p) throws IOException, InterruptedException { - ByteArrayOutputStream bout = new ByteArrayOutputStream(); - try (InputStream in = p.getInputStream(); - InputStream err = p.getErrorStream()) { - int c; - while ((c = in.read()) != -1) { - bout.write(c); - } - while ((c = err.read()) != -1) { - bout.write(c); + private static CharSequence waitAndCapture(Process process) throws IOException, InterruptedException { + StringBuilder result = new StringBuilder(); + try (BufferedReader br = + new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.US_ASCII))) { + for (String line = br.readLine(); line != null; line = br.readLine()) { + result.append(line); } - p.waitFor(); } - return bout.toString(); - } - - /** - * This requires --add-opens java.base/java.lang=ALL-UNNAMED - */ - private ProcessBuilder.Redirect getRedirect(FileDescriptor fd) throws ReflectiveOperationException { - // This is not really allowed, but this is the only way to redirect the output or error stream - // to the input. This is definitely not something you'd usually want to do, but in the case of - // the `tty` utility, it provides a way to get - Class rpi = Class.forName("java.lang.ProcessBuilder$RedirectPipeImpl"); - Constructor cns = rpi.getDeclaredConstructor(); - cns.setAccessible(true); - ProcessBuilder.Redirect input = (ProcessBuilder.Redirect) cns.newInstance(); - Field f = rpi.getDeclaredField("fd"); - f.setAccessible(true); - f.set(input, fd); - return input; + process.waitFor(); + return result; } }