001/*
002 * Copyright (C) 2009-2017 the original author(s).
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.fusesource.jansi;
017
018import java.io.FileDescriptor;
019import java.io.FileOutputStream;
020import java.io.IOError;
021import java.io.IOException;
022import java.io.OutputStream;
023import java.io.PrintStream;
024import java.io.UnsupportedEncodingException;
025import java.nio.charset.Charset;
026import java.nio.charset.UnsupportedCharsetException;
027import java.util.Locale;
028
029import org.fusesource.jansi.internal.CLibrary;
030import org.fusesource.jansi.internal.CLibrary.WinSize;
031import org.fusesource.jansi.io.AnsiOutputStream;
032import org.fusesource.jansi.io.AnsiProcessor;
033import org.fusesource.jansi.io.FastBufferedOutputStream;
034import org.fusesource.jansi.io.WindowsAnsiProcessor;
035import org.fusesource.jansi.internal.Kernel32.CONSOLE_SCREEN_BUFFER_INFO;
036
037import static org.fusesource.jansi.internal.CLibrary.ioctl;
038import static org.fusesource.jansi.internal.CLibrary.isatty;
039import static org.fusesource.jansi.internal.Kernel32.GetConsoleMode;
040import static org.fusesource.jansi.internal.Kernel32.GetStdHandle;
041import static org.fusesource.jansi.internal.Kernel32.STD_ERROR_HANDLE;
042import static org.fusesource.jansi.internal.Kernel32.STD_OUTPUT_HANDLE;
043import static org.fusesource.jansi.internal.Kernel32.SetConsoleMode;
044import static org.fusesource.jansi.internal.Kernel32.GetConsoleScreenBufferInfo;
045
046/**
047 * Provides consistent access to an ANSI aware console PrintStream or an ANSI codes stripping PrintStream
048 * if not on a terminal (see 
049 * <a href="http://fusesource.github.io/jansi/documentation/native-api/index.html?org/fusesource/jansi/internal/CLibrary.html">Jansi native
050 * CLibrary isatty(int)</a>).
051 * <p>The native library used is named <code>jansi</code> and is loaded using <a href="http://fusesource.github.io/hawtjni/">HawtJNI</a> Runtime
052 * <a href="http://fusesource.github.io/hawtjni/documentation/api/index.html?org/fusesource/hawtjni/runtime/Library.html"><code>Library</code></a>
053 *
054 * @author <a href="http://hiramchirino.com">Hiram Chirino</a>
055 * @since 1.0
056 * @see #systemInstall()
057 * @see #out()
058 * @see #err()
059 * @see #ansiStream(boolean) for more details on ANSI mode selection
060 */
061public class AnsiConsole {
062
063    /**
064     * The default mode which Jansi will use, can be either <code>force</code>, <code>strip</code>
065     * or <code>default</code> (the default).
066     * If this property is set, it will override <code>jansi.passthrough</code>,
067     * <code>jansi.strip</code> and <code>jansi.force</code> properties.
068     */
069    public static final String JANSI_MODE = "jansi.mode";
070    /**
071     * Jansi mode specific to the standard output stream.
072     */
073    public static final String JANSI_OUT_MODE = "jansi.out.mode";
074    /**
075     * Jansi mode specific to the standard error stream.
076     */
077    public static final String JANSI_ERR_MODE = "jansi.err.mode";
078
079    /**
080     * Jansi mode value to strip all ansi sequences.
081     */
082    public static final String JANSI_MODE_STRIP = "strip";
083    /**
084     * Jansi mode value to force ansi sequences to the stream even if it's not a terminal.
085     */
086    public static final String JANSI_MODE_FORCE = "force";
087    /**
088     * Jansi mode value that output sequences if on a terminal, else strip them.
089     */
090    public static final String JANSI_MODE_DEFAULT = "default";
091
092    /**
093     * The default color support that Jansi will use, can be either <code>16</code>,
094     * <code>256</code> or <code>truecolor</code>.  If not set, JANSI will try to
095     * autodetect the number of colors supported by the terminal by checking the
096     * <code>COLORTERM</code> and <code>TERM</code> variables.
097     */
098    public static final String JANSI_COLORS = "jansi.colors";
099    /**
100     * Jansi colors specific to the standard output stream.
101     */
102    public static final String JANSI_OUT_COLORS = "jansi.out.colors";
103    /**
104     * Jansi colors specific to the standard error stream.
105     */
106    public static final String JANSI_ERR_COLORS = "jansi.err.colors";
107
108    /**
109     * Force the use of 16 colors. When using a 256-indexed color, or an RGB
110     * color, the color will be rounded to the nearest one from the 16 palette.
111     */
112    public static final String JANSI_COLORS_16 = "16";
113    /**
114     * Force the use of 256 colors. When using an RGB color, the color will be
115     * rounded to the nearest one from the standard 256 palette.
116     */
117    public static final String JANSI_COLORS_256 = "256";
118    /**
119     * Force the use of 24-bit colors.
120     */
121    public static final String JANSI_COLORS_TRUECOLOR = "truecolor";
122
123    /**
124     * If the <code>jansi.passthrough</code> system property is set to true, will not perform any transformation
125     * and any ansi sequence will be conveyed without any modification.
126     *
127     * @deprecated use {@link #JANSI_MODE} instead
128     */
129    @Deprecated
130    public static final String JANSI_PASSTHROUGH = "jansi.passthrough";
131    /**
132     * If the <code>jansi.strip</code> system property is set to true, and <code>jansi.passthrough</code>
133     * is not enabled, all ansi sequences will be stripped before characters are written to the output streams.
134     *
135     * @deprecated use {@link #JANSI_MODE} instead
136     */
137    @Deprecated
138    public static final String JANSI_STRIP = "jansi.strip";
139    /**
140     * If the <code>jansi.force</code> system property is set to true, and neither <code>jansi.passthrough</code>
141     * nor <code>jansi.strip</code> are set, then ansi sequences will be printed to the output stream.
142     * This forces the behavior which is by default dependent on the output stream being a real console: if the
143     * output stream is redirected to a file or through a system pipe, ansi sequences are disabled by default.
144     *
145     * @deprecated use {@link #JANSI_MODE} instead
146     */
147    @Deprecated
148    public static final String JANSI_FORCE = "jansi.force";
149    /**
150     * If the <code>jansi.eager</code> system property is set to true, the system streams will be eagerly
151     * initialized, else the initialization is delayed until {@link #out()}, {@link #err()} or {@link #systemInstall()}
152     * is called.
153     *
154     * @deprecated this property has been added but only for backward compatibility.
155     * @since 2.1
156     */
157    @Deprecated()
158    public static final String JANSI_EAGER = "jansi.eager";
159    /**
160     * If the <code>jansi.noreset</code> system property is set to true, the attributes won't be
161     * reset when the streams are uninstalled.
162     */
163    public static final String JANSI_NORESET = "jansi.noreset";
164    /**
165     * If the <code>jansi.graceful</code> system property is set to false, any exception that occurs
166     * during the initialization will cause the library to report this exception and fail. The default
167     * behavior is to behave gracefully and fall back to pure emulation on posix systems.
168     */
169    public static final String JANSI_GRACEFUL = "jansi.graceful";
170
171    /**
172     * @deprecated this field will be made private in a future release, use {@link #sysOut()} instead
173     */
174    @Deprecated
175    public static PrintStream system_out = System.out;
176    /**
177     * @deprecated this field will be made private in a future release, use {@link #out()} instead
178     */
179    @Deprecated
180    public static PrintStream out;
181    /**
182     * @deprecated this field will be made private in a future release, use {@link #sysErr()} instead
183     */
184    @Deprecated
185    public static PrintStream system_err = System.err;
186    /**
187     * @deprecated this field will be made private in a future release, use {@link #err()} instead
188     */
189    @Deprecated
190    public static PrintStream err;
191
192    /**
193     * Try to find the width of the console for this process.
194     * Both output and error streams will be checked to determine the width.
195     * A value of 0 is returned if the width can not be determined.
196     * @since 2.2
197     */
198    public static int getTerminalWidth() {
199        int w = out().getTerminalWidth();
200        if (w <= 0) {
201            w = err().getTerminalWidth();
202        }
203        return w;
204    }
205
206    static final boolean IS_WINDOWS = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("win");
207
208    static final boolean IS_CYGWIN = IS_WINDOWS
209            && System.getenv("PWD") != null
210            && System.getenv("PWD").startsWith("/");
211
212    static final boolean IS_MSYSTEM = IS_WINDOWS
213            && System.getenv("MSYSTEM") != null
214            && (System.getenv("MSYSTEM").startsWith("MINGW")
215                || System.getenv("MSYSTEM").equals("MSYS"));
216
217    static final boolean IS_CONEMU = IS_WINDOWS
218            && System.getenv("ConEmuPID") != null;
219
220    static final int ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
221
222    static int STDOUT_FILENO = 1;
223
224    static int STDERR_FILENO = 2;
225
226
227    static {
228        if (getBoolean(JANSI_EAGER)) {
229            initStreams();
230        }
231    }
232
233    private static boolean initialized;
234    private static int installed;
235    private static int virtualProcessing;
236
237    private AnsiConsole() {
238    }
239
240    private static AnsiPrintStream ansiStream(boolean stdout) {
241        FileDescriptor descriptor = stdout ? FileDescriptor.out : FileDescriptor.err;
242        final OutputStream out = new FastBufferedOutputStream(new FileOutputStream(descriptor));
243
244        String enc = System.getProperty(stdout ? "sun.stdout.encoding" : "sun.stderr.encoding");
245
246        final boolean isatty;
247        boolean isAtty;
248        boolean withException;
249        // Do not use the CLibrary.STDOUT_FILENO to avoid errors in case
250        // the library can not be loaded on unsupported platforms
251        final int fd = stdout ? STDOUT_FILENO : STDERR_FILENO;
252        try {
253            // If we can detect that stdout is not a tty.. then setup
254            // to strip the ANSI sequences..
255            isAtty = isatty(fd) != 0;
256            String term = System.getenv("TERM");
257            String emacs = System.getenv("INSIDE_EMACS");
258            if (isAtty && "dumb".equals(term) && emacs != null && !emacs.contains("comint")) {
259                isAtty = false;
260            }
261            withException = false;
262        } catch (Throwable ignore) {
263            // These errors happen if the JNI lib is not available for your platform.
264            // But since we are on ANSI friendly platform, assume the user is on the console.
265            isAtty = false;
266            withException = true;
267        }
268        isatty = isAtty;
269
270        final AnsiOutputStream.WidthSupplier width;
271        final AnsiProcessor processor;
272        final AnsiType type;
273        final AnsiOutputStream.IoRunnable installer;
274        final AnsiOutputStream.IoRunnable uninstaller;
275        if (!isatty) {
276            processor = null;
277            type = withException ? AnsiType.Unsupported : AnsiType.Redirected;
278            installer = uninstaller = null;
279            width = new AnsiOutputStream.ZeroWidthSupplier();
280        }
281        else if (IS_WINDOWS) {
282            final long console = GetStdHandle(stdout ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE);
283            final int[] mode = new int[1];
284            final boolean isConsole = GetConsoleMode(console, mode) != 0;
285            if (isConsole
286                    && SetConsoleMode(console, mode[0] | ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0) {
287                SetConsoleMode(console, mode[0]); // set it back for now, but we know it works
288                processor = null;
289                type = AnsiType.VirtualTerminal;
290                installer = new AnsiOutputStream.IoRunnable() {
291                    @Override
292                    public void run() throws IOException {
293                        virtualProcessing++;
294                        SetConsoleMode(console, mode[0] | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
295                    }
296                };
297                uninstaller = new AnsiOutputStream.IoRunnable() {
298                    @Override
299                    public void run() throws IOException {
300                        if (--virtualProcessing == 0) {
301                            SetConsoleMode(console, mode[0]);
302                        }
303                    }
304                };
305            }
306            else if ((IS_CONEMU || IS_CYGWIN || IS_MSYSTEM) && !isConsole) {
307                // ANSI-enabled ConEmu, Cygwin or MSYS(2) on Windows...
308                processor = null;
309                type = AnsiType.Native;
310                installer = uninstaller = null;
311            }
312            else {
313                // On Windows, when no ANSI-capable terminal is used, we know the console does not natively interpret ANSI
314                // codes but we can use jansi Kernel32 API for console
315                AnsiProcessor proc;
316                AnsiType ttype;
317                try {
318                    proc = new WindowsAnsiProcessor(out, console);
319                    ttype = AnsiType.Emulation;
320                } catch (Throwable ignore) {
321                    // this happens when the stdout is being redirected to a file.
322                    // Use the AnsiProcessor to strip out the ANSI escape sequences.
323                    proc = new AnsiProcessor(out);
324                    ttype = AnsiType.Unsupported;
325                }
326                processor = proc;
327                type = ttype;
328                installer = uninstaller = null;
329            }
330            width = new AnsiOutputStream.WidthSupplier() {
331                @Override
332                public int getTerminalWidth() {
333                    CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO();
334                    GetConsoleScreenBufferInfo(console, info);
335                    return info.windowWidth();
336                }
337            };
338        }
339
340        // We must be on some Unix variant...
341        else {
342            // ANSI-enabled ConEmu, Cygwin or MSYS(2) on Windows...
343            processor = null;
344            type = AnsiType.Native;
345            installer = uninstaller = null;
346            width = new AnsiOutputStream.WidthSupplier() {
347                @Override
348                public int getTerminalWidth() {
349                    WinSize sz = new WinSize();
350                    ioctl(fd, CLibrary.TIOCGWINSZ, sz);
351                    return sz.ws_col;
352                }
353            };
354        }
355
356        AnsiMode mode;
357
358        // If the jansi.mode property is set, use it
359        String jansiMode = System.getProperty(stdout ? JANSI_OUT_MODE : JANSI_ERR_MODE, System.getProperty(JANSI_MODE));
360        if (JANSI_MODE_FORCE.equals(jansiMode)) {
361            mode = AnsiMode.Force;
362        } else if (JANSI_MODE_STRIP.equals(jansiMode)) {
363            mode = AnsiMode.Strip;
364        } else if (jansiMode != null) {
365            mode = isatty ? AnsiMode.Default : AnsiMode.Strip;
366        }
367
368        // If the jansi.passthrough property is set, then don't interpret
369        // any of the ansi sequences.
370        else if (getBoolean(JANSI_PASSTHROUGH)) {
371            mode = AnsiMode.Force;
372        }
373
374        // If the jansi.strip property is set, then we just strip the
375        // the ansi escapes.
376        else if (getBoolean(JANSI_STRIP)) {
377            mode = AnsiMode.Strip;
378        }
379
380        // If the jansi.force property is set, then we force to output
381        // the ansi escapes for piping it into ansi color aware commands (e.g. less -r)
382        else if (getBoolean(JANSI_FORCE)) {
383            mode = AnsiMode.Force;
384        }
385
386        else {
387            mode = isatty ? AnsiMode.Default : AnsiMode.Strip;
388        }
389
390        AnsiColors colors;
391
392        String colorterm, term;
393        // If the jansi.colors property is set, use it
394        String jansiColors = System.getProperty(stdout ? JANSI_OUT_COLORS : JANSI_ERR_COLORS, System.getProperty(JANSI_COLORS));
395        if (JANSI_COLORS_TRUECOLOR.equals(jansiColors)) {
396            colors = AnsiColors.TrueColor;
397        } else if (JANSI_COLORS_256.equals(jansiColors)) {
398            colors = AnsiColors.Colors256;
399        } else if (jansiColors != null) {
400            colors = AnsiColors.Colors16;
401        }
402
403        // If the COLORTERM env variable contains "truecolor" or "24bit", assume true color support
404        // see https://gist.github.com/XVilka/8346728#true-color-detection
405        else if ((colorterm = System.getenv("COLORTERM")) != null
406                && (colorterm.contains("truecolor") || colorterm.contains("24bit"))) {
407            colors = AnsiColors.TrueColor;
408        }
409
410        // check the if TERM contains -direct
411        else if ((term = System.getenv("TERM")) != null && term.contains("-direct")) {
412            colors = AnsiColors.TrueColor;
413        }
414
415        // check the if TERM contains -256color
416        else if (term != null && term.contains("-256color")) {
417            colors = AnsiColors.Colors256;
418        }
419
420        // else defaults to 16 colors
421        else {
422            colors = AnsiColors.Colors16;
423        }
424
425        // If the jansi.noreset property is not set, reset the attributes
426        // when the stream is closed
427        boolean resetAtUninstall = type != AnsiType.Unsupported && !getBoolean(JANSI_NORESET);
428
429        Charset cs = Charset.defaultCharset();
430        if (enc != null) {
431            try {
432                cs = Charset.forName(enc);
433            } catch (UnsupportedCharsetException e) {
434            }
435        }
436        return newPrintStream(new AnsiOutputStream(out, width, mode, processor, type, colors, cs,
437                installer, uninstaller, resetAtUninstall), cs.name());
438    }
439
440    private static AnsiPrintStream newPrintStream(AnsiOutputStream out, String enc) {
441        if (enc != null) {
442            try {
443                return new AnsiPrintStream(out, true, enc);
444            } catch (UnsupportedEncodingException e) {
445            }
446        }
447        return new AnsiPrintStream(out, true);
448    }
449
450    static boolean getBoolean(String name) {
451        boolean result = false;
452        try {
453            String val = System.getProperty(name);
454            result = val.isEmpty() || Boolean.parseBoolean(val);
455        } catch (IllegalArgumentException e) {
456        } catch (NullPointerException e) {
457        }
458        return result;
459    }
460
461    /**
462     * If the standard out natively supports ANSI escape codes, then this just
463     * returns System.out, otherwise it will provide an ANSI aware PrintStream
464     * which strips out the ANSI escape sequences or which implement the escape
465     * sequences.
466     *
467     * @return a PrintStream which is ANSI aware.
468     */
469    public static AnsiPrintStream out() {
470        initStreams();
471        return (AnsiPrintStream) out;
472    }
473
474    /**
475     * Access to the original System.out stream before ansi streams were installed.
476     *
477     * @return the originial System.out print stream
478     */
479    public static PrintStream sysOut() {
480        return system_out;
481    }
482
483    /**
484     * If the standard out natively supports ANSI escape codes, then this just
485     * returns System.err, otherwise it will provide an ANSI aware PrintStream
486     * which strips out the ANSI escape sequences or which implement the escape
487     * sequences.
488     *
489     * @return a PrintStream which is ANSI aware.
490     */
491    public static AnsiPrintStream err() {
492        initStreams();
493        return (AnsiPrintStream) err;
494    }
495
496    /**
497     * Access to the original System.err stream before ansi streams were installed.
498     *
499     * @return the originial System.err print stream
500     */
501    public static PrintStream sysErr() {
502        return system_err;
503    }
504
505    /**
506     * Install <code>AnsiConsole.out()</code> to <code>System.out</code> and
507     * <code>AnsiConsole.err()</code> to <code>System.err</code>.
508     * @see #systemUninstall()
509     */
510    synchronized static public void systemInstall() {
511        installed++;
512        if (installed == 1) {
513            initStreams();
514            try {
515                ((AnsiPrintStream) out).install();
516                ((AnsiPrintStream) err).install();
517            } catch (IOException e) {
518                throw new IOError(e);
519            }
520            System.setOut(out);
521            System.setErr(err);
522        }
523    }
524
525    /**
526     * check if the streams have been installed or not
527     */
528    synchronized public static boolean isInstalled() {
529        return installed > 0;
530    }
531
532    /**
533     * undo a previous {@link #systemInstall()}.  If {@link #systemInstall()} was called
534     * multiple times, {@link #systemUninstall()} must be called the same number of times before
535     * it is actually uninstalled.
536     */
537    synchronized public static void systemUninstall() {
538        installed--;
539        if (installed == 0) {
540            try {
541                ((AnsiPrintStream) out).uninstall();
542                ((AnsiPrintStream) err).uninstall();
543            } catch (IOException e) {
544                throw new IOError(e);
545            }
546            initialized = false;
547            System.setOut(system_out);
548            System.setErr(system_err);
549        }
550    }
551
552    /**
553     * Initialize the out/err ansi-enabled streams
554     */
555    synchronized static void initStreams()
556    {
557        if ( !initialized )
558        {
559            out = ansiStream(true);
560            err = ansiStream(false);
561            initialized = true;
562        }
563    }
564
565    ;
566}