From 57aa84d4120395922458f61f20e741198f051087 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Fri, 23 Oct 2020 09:55:42 +0200 Subject: [PATCH] Fix windows support, do not use reflection while creating the system out/err print streams This has the downside of not playing well if the user has overriden the system streams previously, but any kind of wrappers around the streams should happen after JANSI processing --- .../org/fusesource/jansi/AnsiConsole.java | 133 +++++++++++++++++- 1 file changed, 129 insertions(+), 4 deletions(-) diff --git a/jansi/src/main/java/org/fusesource/jansi/AnsiConsole.java b/jansi/src/main/java/org/fusesource/jansi/AnsiConsole.java index 0721ddff..9a8978f5 100644 --- a/jansi/src/main/java/org/fusesource/jansi/AnsiConsole.java +++ b/jansi/src/main/java/org/fusesource/jansi/AnsiConsole.java @@ -16,11 +16,14 @@ package org.fusesource.jansi; import java.io.BufferedOutputStream; +import java.io.FileDescriptor; +import java.io.FileOutputStream; import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintStream; +import java.io.UnsupportedEncodingException; import java.lang.reflect.Field; import java.nio.charset.Charset; import java.util.Locale; @@ -28,6 +31,11 @@ import static org.fusesource.jansi.internal.CLibrary.STDERR_FILENO; import static org.fusesource.jansi.internal.CLibrary.STDOUT_FILENO; import static org.fusesource.jansi.internal.CLibrary.isatty; +import static org.fusesource.jansi.internal.Kernel32.GetConsoleMode; +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; /** * Provides consistent access to an ANSI aware console PrintStream or an ANSI codes stripping PrintStream @@ -56,25 +64,36 @@ public class AnsiConsole { * ConEmu ANSI X3.64 support enabled, * used by cmder */ + @Deprecated static final boolean IS_CON_EMU_ANSI = "ON".equals(System.getenv("ConEmuANSI")); static final boolean IS_CYGWIN = IS_WINDOWS && System.getenv("PWD") != null - && System.getenv("PWD").startsWith("/") - && !"cygwin".equals(System.getenv("TERM")); + && System.getenv("PWD").startsWith("/"); + @Deprecated static final boolean IS_MINGW_XTERM = IS_WINDOWS && System.getenv("MSYSTEM") != null && System.getenv("MSYSTEM").startsWith("MINGW") && "xterm".equals(System.getenv("TERM")); + static final boolean IS_MSYSTEM = IS_WINDOWS + && System.getenv("MSYSTEM") != null + && (System.getenv("MSYSTEM").startsWith("MINGW") + || System.getenv("MSYSTEM").equals("MSYS")); + + static final boolean IS_CONEMU = IS_WINDOWS + && System.getenv("ConEmuPID") != null; + + static final int ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004; + private static JansiOutputType jansiOutputType; static final JansiOutputType JANSI_STDOUT_TYPE; static final JansiOutputType JANSI_STDERR_TYPE; static { - out = wrapSystemOut(system_out); + out = ansiSystem(true); JANSI_STDOUT_TYPE = jansiOutputType; - err = wrapSystemErr(system_err); + err = ansiSystem(false); JANSI_STDERR_TYPE = jansiOutputType; } @@ -83,6 +102,112 @@ public class AnsiConsole { private AnsiConsole() { } + private static PrintStream ansiSystem(boolean stdout) { + OutputStream out = new BufferedNoSyncOutputStream(new FileOutputStream(stdout ? FileDescriptor.out : FileDescriptor.err)); + + String enc = System.getProperty(stdout ? "sun.stdout.encoding" : "sun.stderr.encoding"); + + // If the jansi.passthrough property is set, then don't interpret + // any of the ansi sequences. + if (Boolean.getBoolean("jansi.passthrough")) { + jansiOutputType = JansiOutputType.PASSTHROUGH; + return newPrintStream(out, enc); + } + + // If the jansi.strip property is set, then we just strip the + // the ansi escapes. + if (Boolean.getBoolean("jansi.strip")) { + jansiOutputType = JansiOutputType.STRIP_ANSI; + return new PrintStream(new AnsiNoSyncOutputStream(out, new AnsiProcessor(out)), true); + } + + if (IS_WINDOWS) { + long console = GetStdHandle(stdout ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE); + int[] mode = new int[1]; + if (GetConsoleMode(console, mode) != 0 + && SetConsoleMode(console, mode[0] | ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0) { + jansiOutputType = JansiOutputType.PASSTHROUGH; + return newPrintStream(out, enc); + } + } + + if (IS_WINDOWS && !(IS_CON_EMU_ANSI || IS_CYGWIN || IS_MINGW_XTERM)) { + + // On Windows, when no ANSI-capable terminal is used, we know the console does not natively interpret ANSI + // codes but we can use jansi-native Kernel32 API for console + try { + jansiOutputType = JansiOutputType.WINDOWS; + return newPrintStream(new AnsiNoSyncOutputStream(out, new WindowsAnsiProcessor(out, stdout)), enc); + } catch (Throwable ignore) { + // this happens when JNA is not in the path.. or + // this happens when the stdout is being redirected to a file. + } + + // Use the ANSIOutputStream to strip out the ANSI escape sequences. + jansiOutputType = JansiOutputType.STRIP_ANSI; + return newPrintStream(new AnsiNoSyncOutputStream(out, new AnsiProcessor(out)), enc); + } + + // We must be on some Unix variant or ANSI-enabled ConEmu, Cygwin or MSYS(2) on Windows... + try { + // If the jansi.force property is set, then we force to output + // the ansi escapes for piping it into ansi color aware commands (e.g. less -r) + boolean forceColored = Boolean.getBoolean("jansi.force"); + // If we can detect that stdout is not a tty.. then setup + // to strip the ANSI sequences.. + if (!forceColored && isatty(stdout ? 1 : 2) == 0) { + jansiOutputType = JansiOutputType.STRIP_ANSI; + return newPrintStream(new AnsiNoSyncOutputStream(out, new AnsiProcessor(out)), enc); + } + } catch (Throwable ignore) { + // These errors happen if the JNI lib is not available for your platform. + // But since we are on ANSI friendly platform, assume the user is on the console. + } + + // By default we assume your Unix tty can handle ANSI codes. + // Just wrap it up so that when we get closed, we reset the + // attributes. + jansiOutputType = JansiOutputType.RESET_ANSI_AT_CLOSE; + return newPrintStream(out, enc, AnsiNoSyncOutputStream.RESET_CODE); + } + + private static PrintStream newPrintStream(OutputStream out, String enc) { + return newPrintStream(out, enc, null); + } + + private static PrintStream newPrintStream(OutputStream out, String enc, byte[] reset) { + if (enc != null) { + try { + return new ResetAtClosePrintStream(out, enc, reset); + } catch (UnsupportedEncodingException e) { + } + } + return new ResetAtClosePrintStream(out, reset); + } + + static class ResetAtClosePrintStream extends PrintStream { + + private final byte[] reset; + + public ResetAtClosePrintStream(OutputStream out, byte[] reset) { + super(out, true); + this.reset = reset; + } + + public ResetAtClosePrintStream(OutputStream out, String encoding, byte[] reset) throws UnsupportedEncodingException { + super(out, true, encoding); + this.reset = reset; + } + + @Override + public void close() { + if (reset != null) { + write(reset, 0, reset.length); + } + super.close(); + } + } + @Deprecated public static OutputStream wrapOutputStream(final OutputStream stream) { try {