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