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 static org.fusesource.jansi.internal.Kernel32.BACKGROUND_BLUE; 019import static org.fusesource.jansi.internal.Kernel32.BACKGROUND_GREEN; 020import static org.fusesource.jansi.internal.Kernel32.BACKGROUND_INTENSITY; 021import static org.fusesource.jansi.internal.Kernel32.BACKGROUND_RED; 022import static org.fusesource.jansi.internal.Kernel32.CHAR_INFO; 023import static org.fusesource.jansi.internal.Kernel32.FOREGROUND_BLUE; 024import static org.fusesource.jansi.internal.Kernel32.FOREGROUND_GREEN; 025import static org.fusesource.jansi.internal.Kernel32.FOREGROUND_INTENSITY; 026import static org.fusesource.jansi.internal.Kernel32.FOREGROUND_RED; 027import static org.fusesource.jansi.internal.Kernel32.FillConsoleOutputAttribute; 028import static org.fusesource.jansi.internal.Kernel32.FillConsoleOutputCharacterW; 029import static org.fusesource.jansi.internal.Kernel32.GetConsoleScreenBufferInfo; 030import static org.fusesource.jansi.internal.Kernel32.GetStdHandle; 031import static org.fusesource.jansi.internal.Kernel32.SMALL_RECT; 032import static org.fusesource.jansi.internal.Kernel32.STD_OUTPUT_HANDLE; 033import static org.fusesource.jansi.internal.Kernel32.ScrollConsoleScreenBuffer; 034import static org.fusesource.jansi.internal.Kernel32.SetConsoleCursorPosition; 035import static org.fusesource.jansi.internal.Kernel32.SetConsoleTextAttribute; 036import static org.fusesource.jansi.internal.Kernel32.SetConsoleTitle; 037 038import java.io.IOException; 039import java.io.PrintStream; // expected diff with WindowsAnsiOutputStream.java 040 041import org.fusesource.jansi.internal.WindowsSupport; 042import org.fusesource.jansi.internal.Kernel32.CONSOLE_SCREEN_BUFFER_INFO; 043import org.fusesource.jansi.internal.Kernel32.COORD; 044 045/** 046 * A Windows ANSI escape processor, that uses JNA to access native platform 047 * API's to change the console attributes (see 048 * <a href="http://fusesource.github.io/jansi/documentation/native-api/index.html?org/fusesource/jansi/internal/Kernel32.html">Jansi native Kernel32</a>). 049 * 050 * @since 1.7 051 * @author <a href="http://hiramchirino.com">Hiram Chirino</a> 052 * @author Joris Kuipers 053 * @see WindowsAnsiOutputStream 054 */ 055public final class WindowsAnsiPrintStream extends AnsiPrintStream { // expected diff with WindowsAnsiOutputStream.java 056 057 private static final long console = GetStdHandle(STD_OUTPUT_HANDLE); 058 059 private static final short FOREGROUND_BLACK = 0; 060 private static final short FOREGROUND_YELLOW = (short) (FOREGROUND_RED | FOREGROUND_GREEN); 061 private static final short FOREGROUND_MAGENTA = (short) (FOREGROUND_BLUE | FOREGROUND_RED); 062 private static final short FOREGROUND_CYAN = (short) (FOREGROUND_BLUE | FOREGROUND_GREEN); 063 private static final short FOREGROUND_WHITE = (short) (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); 064 065 private static final short BACKGROUND_BLACK = 0; 066 private static final short BACKGROUND_YELLOW = (short) (BACKGROUND_RED | BACKGROUND_GREEN); 067 private static final short BACKGROUND_MAGENTA = (short) (BACKGROUND_BLUE | BACKGROUND_RED); 068 private static final short BACKGROUND_CYAN = (short) (BACKGROUND_BLUE | BACKGROUND_GREEN); 069 private static final short BACKGROUND_WHITE = (short) (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE); 070 071 private static final short[] ANSI_FOREGROUND_COLOR_MAP = { 072 FOREGROUND_BLACK, 073 FOREGROUND_RED, 074 FOREGROUND_GREEN, 075 FOREGROUND_YELLOW, 076 FOREGROUND_BLUE, 077 FOREGROUND_MAGENTA, 078 FOREGROUND_CYAN, 079 FOREGROUND_WHITE, 080 }; 081 082 private static final short[] ANSI_BACKGROUND_COLOR_MAP = { 083 BACKGROUND_BLACK, 084 BACKGROUND_RED, 085 BACKGROUND_GREEN, 086 BACKGROUND_YELLOW, 087 BACKGROUND_BLUE, 088 BACKGROUND_MAGENTA, 089 BACKGROUND_CYAN, 090 BACKGROUND_WHITE, 091 }; 092 093 private final CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO(); 094 private final short originalColors; 095 096 private boolean negative; 097 private short savedX = -1; 098 private short savedY = -1; 099 100 public WindowsAnsiPrintStream(PrintStream ps) throws IOException { // expected diff with WindowsAnsiOutputStream.java 101 super(ps); // expected diff with WindowsAnsiOutputStream.java 102 getConsoleInfo(); 103 originalColors = info.attributes; 104 } 105 106 private void getConsoleInfo() throws IOException { 107 ps.flush(); // expected diff with WindowsAnsiOutputStream.java 108 if (GetConsoleScreenBufferInfo(console, info) == 0) { 109 throw new IOException("Could not get the screen info: " + WindowsSupport.getLastErrorMessage()); 110 } 111 if (negative) { 112 info.attributes = invertAttributeColors(info.attributes); 113 } 114 } 115 116 private void applyAttribute() throws IOException { 117 ps.flush(); // expected diff with WindowsAnsiOutputStream.java 118 short attributes = info.attributes; 119 if (negative) { 120 attributes = invertAttributeColors(attributes); 121 } 122 if (SetConsoleTextAttribute(console, attributes) == 0) { 123 throw new IOException(WindowsSupport.getLastErrorMessage()); 124 } 125 } 126 127 private short invertAttributeColors(short attributes) { 128 // Swap the the Foreground and Background bits. 129 int fg = 0x000F & attributes; 130 fg <<= 4; 131 int bg = 0X00F0 & attributes; 132 bg >>= 4; 133 attributes = (short) ((attributes & 0xFF00) | fg | bg); 134 return attributes; 135 } 136 137 private void applyCursorPosition() throws IOException { 138 if (SetConsoleCursorPosition(console, info.cursorPosition.copy()) == 0) { 139 throw new IOException(WindowsSupport.getLastErrorMessage()); 140 } 141 } 142 143 @Override 144 protected void processEraseScreen(int eraseOption) throws IOException { 145 getConsoleInfo(); 146 int[] written = new int[1]; 147 switch (eraseOption) { 148 case ERASE_SCREEN: 149 COORD topLeft = new COORD(); 150 topLeft.x = 0; 151 topLeft.y = info.window.top; 152 int screenLength = info.window.height() * info.size.x; 153 FillConsoleOutputAttribute(console, originalColors, screenLength, topLeft, written); 154 FillConsoleOutputCharacterW(console, ' ', screenLength, topLeft, written); 155 break; 156 case ERASE_SCREEN_TO_BEGINING: 157 COORD topLeft2 = new COORD(); 158 topLeft2.x = 0; 159 topLeft2.y = info.window.top; 160 int lengthToCursor = (info.cursorPosition.y - info.window.top) * info.size.x 161 + info.cursorPosition.x; 162 FillConsoleOutputAttribute(console, originalColors, lengthToCursor, topLeft2, written); 163 FillConsoleOutputCharacterW(console, ' ', lengthToCursor, topLeft2, written); 164 break; 165 case ERASE_SCREEN_TO_END: 166 int lengthToEnd = (info.window.bottom - info.cursorPosition.y) * info.size.x + 167 (info.size.x - info.cursorPosition.x); 168 FillConsoleOutputAttribute(console, originalColors, lengthToEnd, info.cursorPosition.copy(), written); 169 FillConsoleOutputCharacterW(console, ' ', lengthToEnd, info.cursorPosition.copy(), written); 170 break; 171 default: 172 break; 173 } 174 } 175 176 @Override 177 protected void processEraseLine(int eraseOption) throws IOException { 178 getConsoleInfo(); 179 int[] written = new int[1]; 180 switch (eraseOption) { 181 case ERASE_LINE: 182 COORD leftColCurrRow = info.cursorPosition.copy(); 183 leftColCurrRow.x = 0; 184 FillConsoleOutputAttribute(console, originalColors, info.size.x, leftColCurrRow, written); 185 FillConsoleOutputCharacterW(console, ' ', info.size.x, leftColCurrRow, written); 186 break; 187 case ERASE_LINE_TO_BEGINING: 188 COORD leftColCurrRow2 = info.cursorPosition.copy(); 189 leftColCurrRow2.x = 0; 190 FillConsoleOutputAttribute(console, originalColors, info.cursorPosition.x, leftColCurrRow2, written); 191 FillConsoleOutputCharacterW(console, ' ', info.cursorPosition.x, leftColCurrRow2, written); 192 break; 193 case ERASE_LINE_TO_END: 194 int lengthToLastCol = info.size.x - info.cursorPosition.x; 195 FillConsoleOutputAttribute(console, originalColors, lengthToLastCol, info.cursorPosition.copy(), written); 196 FillConsoleOutputCharacterW(console, ' ', lengthToLastCol, info.cursorPosition.copy(), written); 197 break; 198 default: 199 break; 200 } 201 } 202 203 @Override 204 protected void processCursorLeft(int count) throws IOException { 205 getConsoleInfo(); 206 info.cursorPosition.x = (short) Math.max(0, info.cursorPosition.x - count); 207 applyCursorPosition(); 208 } 209 210 @Override 211 protected void processCursorRight(int count) throws IOException { 212 getConsoleInfo(); 213 info.cursorPosition.x = (short) Math.min(info.window.width(), info.cursorPosition.x + count); 214 applyCursorPosition(); 215 } 216 217 @Override 218 protected void processCursorDown(int count) throws IOException { 219 getConsoleInfo(); 220 info.cursorPosition.y = (short) Math.min(Math.max(0, info.size.y - 1), info.cursorPosition.y + count); 221 applyCursorPosition(); 222 } 223 224 @Override 225 protected void processCursorUp(int count) throws IOException { 226 getConsoleInfo(); 227 info.cursorPosition.y = (short) Math.max(info.window.top, info.cursorPosition.y - count); 228 applyCursorPosition(); 229 } 230 231 @Override 232 protected void processCursorTo(int row, int col) throws IOException { 233 getConsoleInfo(); 234 info.cursorPosition.y = (short) Math.max(info.window.top, Math.min(info.size.y, info.window.top + row - 1)); 235 info.cursorPosition.x = (short) Math.max(0, Math.min(info.window.width(), col - 1)); 236 applyCursorPosition(); 237 } 238 239 @Override 240 protected void processCursorToColumn(int x) throws IOException { 241 getConsoleInfo(); 242 info.cursorPosition.x = (short) Math.max(0, Math.min(info.window.width(), x - 1)); 243 applyCursorPosition(); 244 } 245 246 @Override 247 protected void processSetForegroundColor(int color, boolean bright) throws IOException { 248 info.attributes = (short) ((info.attributes & ~0x0007) | ANSI_FOREGROUND_COLOR_MAP[color]); 249 if (bright) { 250 info.attributes |= FOREGROUND_INTENSITY; 251 } 252 applyAttribute(); 253 } 254 255 @Override 256 protected void processSetBackgroundColor(int color, boolean bright) throws IOException { 257 info.attributes = (short) ((info.attributes & ~0x0070) | ANSI_BACKGROUND_COLOR_MAP[color]); 258 if (bright) { 259 info.attributes |= BACKGROUND_INTENSITY; 260 } 261 applyAttribute(); 262 } 263 264 @Override 265 protected void processDefaultTextColor() throws IOException { 266 info.attributes = (short) ((info.attributes & ~0x000F) | (originalColors & 0xF)); 267 info.attributes = (short) (info.attributes & ~FOREGROUND_INTENSITY); 268 applyAttribute(); 269 } 270 271 @Override 272 protected void processDefaultBackgroundColor() throws IOException { 273 info.attributes = (short) ((info.attributes & ~0x00F0) | (originalColors & 0xF0)); 274 info.attributes = (short) (info.attributes & ~BACKGROUND_INTENSITY); 275 applyAttribute(); 276 } 277 278 @Override 279 protected void processAttributeRest() throws IOException { 280 info.attributes = (short) ((info.attributes & ~0x00FF) | originalColors); 281 this.negative = false; 282 applyAttribute(); 283 } 284 285 @Override 286 protected void processSetAttribute(int attribute) throws IOException { 287 switch (attribute) { 288 case ATTRIBUTE_INTENSITY_BOLD: 289 info.attributes = (short) (info.attributes | FOREGROUND_INTENSITY); 290 applyAttribute(); 291 break; 292 case ATTRIBUTE_INTENSITY_NORMAL: 293 info.attributes = (short) (info.attributes & ~FOREGROUND_INTENSITY); 294 applyAttribute(); 295 break; 296 297 // Yeah, setting the background intensity is not underlining.. but it's best we can do 298 // using the Windows console API 299 case ATTRIBUTE_UNDERLINE: 300 info.attributes = (short) (info.attributes | BACKGROUND_INTENSITY); 301 applyAttribute(); 302 break; 303 case ATTRIBUTE_UNDERLINE_OFF: 304 info.attributes = (short) (info.attributes & ~BACKGROUND_INTENSITY); 305 applyAttribute(); 306 break; 307 308 case ATTRIBUTE_NEGATIVE_ON: 309 negative = true; 310 applyAttribute(); 311 break; 312 case ATTRIBUTE_NEGATIVE_OFF: 313 negative = false; 314 applyAttribute(); 315 break; 316 default: 317 break; 318 } 319 } 320 321 @Override 322 protected void processSaveCursorPosition() throws IOException { 323 getConsoleInfo(); 324 savedX = info.cursorPosition.x; 325 savedY = info.cursorPosition.y; 326 } 327 328 @Override 329 protected void processRestoreCursorPosition() throws IOException { 330 // restore only if there was a save operation first 331 if (savedX != -1 && savedY != -1) { 332 ps.flush(); // expected diff with WindowsAnsiOutputStream.java 333 info.cursorPosition.x = savedX; 334 info.cursorPosition.y = savedY; 335 applyCursorPosition(); 336 } 337 } 338 339 @Override 340 protected void processInsertLine(int optionInt) throws IOException { 341 getConsoleInfo(); 342 SMALL_RECT scroll = info.window.copy(); 343 scroll.top = info.cursorPosition.y; 344 COORD org = new COORD(); 345 org.x = 0; 346 org.y = (short)(info.cursorPosition.y + optionInt); 347 CHAR_INFO info = new CHAR_INFO(); 348 info.attributes = originalColors; 349 info.unicodeChar = ' '; 350 if (ScrollConsoleScreenBuffer(console, scroll, scroll, org, info) == 0) { 351 throw new IOException(WindowsSupport.getLastErrorMessage()); 352 } 353 } 354 355 @Override 356 protected void processDeleteLine(int optionInt) throws IOException { 357 getConsoleInfo(); 358 SMALL_RECT scroll = info.window.copy(); 359 scroll.top = info.cursorPosition.y; 360 COORD org = new COORD(); 361 org.x = 0; 362 org.y = (short)(info.cursorPosition.y - optionInt); 363 CHAR_INFO info = new CHAR_INFO(); 364 info.attributes = originalColors; 365 info.unicodeChar = ' '; 366 if (ScrollConsoleScreenBuffer(console, scroll, scroll, org, info) == 0) { 367 throw new IOException(WindowsSupport.getLastErrorMessage()); 368 } 369 } 370 371 @Override 372 protected void processChangeWindowTitle(String label) { 373 SetConsoleTitle(label); 374 } 375}