Skip to content

Commit 57aa84d

Browse files
committed
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
1 parent 07916c1 commit 57aa84d

File tree

1 file changed

+129
-4
lines changed

1 file changed

+129
-4
lines changed

jansi/src/main/java/org/fusesource/jansi/AnsiConsole.java

Lines changed: 129 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,26 @@
1616
package org.fusesource.jansi;
1717

1818
import java.io.BufferedOutputStream;
19+
import java.io.FileDescriptor;
20+
import java.io.FileOutputStream;
1921
import java.io.FilterOutputStream;
2022
import java.io.IOException;
2123
import java.io.OutputStream;
2224
import java.io.OutputStreamWriter;
2325
import java.io.PrintStream;
26+
import java.io.UnsupportedEncodingException;
2427
import java.lang.reflect.Field;
2528
import java.nio.charset.Charset;
2629
import java.util.Locale;
2730

2831
import static org.fusesource.jansi.internal.CLibrary.STDERR_FILENO;
2932
import static org.fusesource.jansi.internal.CLibrary.STDOUT_FILENO;
3033
import static org.fusesource.jansi.internal.CLibrary.isatty;
34+
import static org.fusesource.jansi.internal.Kernel32.GetConsoleMode;
35+
import static org.fusesource.jansi.internal.Kernel32.GetStdHandle;
36+
import static org.fusesource.jansi.internal.Kernel32.STD_ERROR_HANDLE;
37+
import static org.fusesource.jansi.internal.Kernel32.STD_OUTPUT_HANDLE;
38+
import static org.fusesource.jansi.internal.Kernel32.SetConsoleMode;
3139

3240
/**
3341
* Provides consistent access to an ANSI aware console PrintStream or an ANSI codes stripping PrintStream
@@ -56,25 +64,36 @@ public class AnsiConsole {
5664
* <a href="https://conemu.github.io">ConEmu</a> ANSI X3.64 support enabled,
5765
* used by <a href="https://cmder.net/">cmder</a>
5866
*/
67+
@Deprecated
5968
static final boolean IS_CON_EMU_ANSI = "ON".equals(System.getenv("ConEmuANSI"));
6069

6170
static final boolean IS_CYGWIN = IS_WINDOWS
6271
&& System.getenv("PWD") != null
63-
&& System.getenv("PWD").startsWith("/")
64-
&& !"cygwin".equals(System.getenv("TERM"));
72+
&& System.getenv("PWD").startsWith("/");
6573

74+
@Deprecated
6675
static final boolean IS_MINGW_XTERM = IS_WINDOWS
6776
&& System.getenv("MSYSTEM") != null
6877
&& System.getenv("MSYSTEM").startsWith("MINGW")
6978
&& "xterm".equals(System.getenv("TERM"));
7079

80+
static final boolean IS_MSYSTEM = IS_WINDOWS
81+
&& System.getenv("MSYSTEM") != null
82+
&& (System.getenv("MSYSTEM").startsWith("MINGW")
83+
|| System.getenv("MSYSTEM").equals("MSYS"));
84+
85+
static final boolean IS_CONEMU = IS_WINDOWS
86+
&& System.getenv("ConEmuPID") != null;
87+
88+
static final int ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
89+
7190
private static JansiOutputType jansiOutputType;
7291
static final JansiOutputType JANSI_STDOUT_TYPE;
7392
static final JansiOutputType JANSI_STDERR_TYPE;
7493
static {
75-
out = wrapSystemOut(system_out);
94+
out = ansiSystem(true);
7695
JANSI_STDOUT_TYPE = jansiOutputType;
77-
err = wrapSystemErr(system_err);
96+
err = ansiSystem(false);
7897
JANSI_STDERR_TYPE = jansiOutputType;
7998
}
8099

@@ -83,6 +102,112 @@ public class AnsiConsole {
83102
private AnsiConsole() {
84103
}
85104

105+
private static PrintStream ansiSystem(boolean stdout) {
106+
OutputStream out = new BufferedNoSyncOutputStream(new FileOutputStream(stdout ? FileDescriptor.out : FileDescriptor.err));
107+
108+
String enc = System.getProperty(stdout ? "sun.stdout.encoding" : "sun.stderr.encoding");
109+
110+
// If the jansi.passthrough property is set, then don't interpret
111+
// any of the ansi sequences.
112+
if (Boolean.getBoolean("jansi.passthrough")) {
113+
jansiOutputType = JansiOutputType.PASSTHROUGH;
114+
return newPrintStream(out, enc);
115+
}
116+
117+
// If the jansi.strip property is set, then we just strip the
118+
// the ansi escapes.
119+
if (Boolean.getBoolean("jansi.strip")) {
120+
jansiOutputType = JansiOutputType.STRIP_ANSI;
121+
return new PrintStream(new AnsiNoSyncOutputStream(out, new AnsiProcessor(out)), true);
122+
}
123+
124+
if (IS_WINDOWS) {
125+
long console = GetStdHandle(stdout ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE);
126+
int[] mode = new int[1];
127+
if (GetConsoleMode(console, mode) != 0
128+
&& SetConsoleMode(console, mode[0] | ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0) {
129+
jansiOutputType = JansiOutputType.PASSTHROUGH;
130+
return newPrintStream(out, enc);
131+
}
132+
}
133+
134+
if (IS_WINDOWS && !(IS_CON_EMU_ANSI || IS_CYGWIN || IS_MINGW_XTERM)) {
135+
136+
// On Windows, when no ANSI-capable terminal is used, we know the console does not natively interpret ANSI
137+
// codes but we can use jansi-native Kernel32 API for console
138+
try {
139+
jansiOutputType = JansiOutputType.WINDOWS;
140+
return newPrintStream(new AnsiNoSyncOutputStream(out, new WindowsAnsiProcessor(out, stdout)), enc);
141+
} catch (Throwable ignore) {
142+
// this happens when JNA is not in the path.. or
143+
// this happens when the stdout is being redirected to a file.
144+
}
145+
146+
// Use the ANSIOutputStream to strip out the ANSI escape sequences.
147+
jansiOutputType = JansiOutputType.STRIP_ANSI;
148+
return newPrintStream(new AnsiNoSyncOutputStream(out, new AnsiProcessor(out)), enc);
149+
}
150+
151+
// We must be on some Unix variant or ANSI-enabled ConEmu, Cygwin or MSYS(2) on Windows...
152+
try {
153+
// If the jansi.force property is set, then we force to output
154+
// the ansi escapes for piping it into ansi color aware commands (e.g. less -r)
155+
boolean forceColored = Boolean.getBoolean("jansi.force");
156+
// If we can detect that stdout is not a tty.. then setup
157+
// to strip the ANSI sequences..
158+
if (!forceColored && isatty(stdout ? 1 : 2) == 0) {
159+
jansiOutputType = JansiOutputType.STRIP_ANSI;
160+
return newPrintStream(new AnsiNoSyncOutputStream(out, new AnsiProcessor(out)), enc);
161+
}
162+
} catch (Throwable ignore) {
163+
// These errors happen if the JNI lib is not available for your platform.
164+
// But since we are on ANSI friendly platform, assume the user is on the console.
165+
}
166+
167+
// By default we assume your Unix tty can handle ANSI codes.
168+
// Just wrap it up so that when we get closed, we reset the
169+
// attributes.
170+
jansiOutputType = JansiOutputType.RESET_ANSI_AT_CLOSE;
171+
return newPrintStream(out, enc, AnsiNoSyncOutputStream.RESET_CODE);
172+
}
173+
174+
private static PrintStream newPrintStream(OutputStream out, String enc) {
175+
return newPrintStream(out, enc, null);
176+
}
177+
178+
private static PrintStream newPrintStream(OutputStream out, String enc, byte[] reset) {
179+
if (enc != null) {
180+
try {
181+
return new ResetAtClosePrintStream(out, enc, reset);
182+
} catch (UnsupportedEncodingException e) {
183+
}
184+
}
185+
return new ResetAtClosePrintStream(out, reset);
186+
}
187+
188+
static class ResetAtClosePrintStream extends PrintStream {
189+
190+
private final byte[] reset;
191+
192+
public ResetAtClosePrintStream(OutputStream out, byte[] reset) {
193+
super(out, true);
194+
this.reset = reset;
195+
}
196+
197+
public ResetAtClosePrintStream(OutputStream out, String encoding, byte[] reset) throws UnsupportedEncodingException {
198+
super(out, true, encoding);
199+
this.reset = reset;
200+
}
201+
202+
@Override
203+
public void close() {
204+
if (reset != null) {
205+
write(reset, 0, reset.length);
206+
}
207+
super.close();
208+
}
209+
}
210+
86211
@Deprecated
87212
public static OutputStream wrapOutputStream(final OutputStream stream) {
88213
try {

0 commit comments

Comments
 (0)