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}