001 /** 002 * Copyright (C) 2009, Progress Software Corporation and/or its 003 * subsidiaries or affiliates. All rights reserved. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.fusesource.jansi; 018 019 import static org.fusesource.jansi.internal.Kernel32.BACKGROUND_BLUE; 020 import static org.fusesource.jansi.internal.Kernel32.BACKGROUND_GREEN; 021 import static org.fusesource.jansi.internal.Kernel32.BACKGROUND_INTENSITY; 022 import static org.fusesource.jansi.internal.Kernel32.BACKGROUND_RED; 023 import static org.fusesource.jansi.internal.Kernel32.FOREGROUND_BLUE; 024 import static org.fusesource.jansi.internal.Kernel32.FOREGROUND_GREEN; 025 import static org.fusesource.jansi.internal.Kernel32.FOREGROUND_INTENSITY; 026 import static org.fusesource.jansi.internal.Kernel32.FOREGROUND_RED; 027 import static org.fusesource.jansi.internal.Kernel32.FillConsoleOutputCharacterW; 028 import static org.fusesource.jansi.internal.Kernel32.GetConsoleScreenBufferInfo; 029 import static org.fusesource.jansi.internal.Kernel32.GetStdHandle; 030 import static org.fusesource.jansi.internal.Kernel32.STD_OUTPUT_HANDLE; 031 import static org.fusesource.jansi.internal.Kernel32.SetConsoleCursorPosition; 032 import static org.fusesource.jansi.internal.Kernel32.SetConsoleTextAttribute; 033 034 import java.io.IOException; 035 import java.io.OutputStream; 036 037 import org.fusesource.jansi.internal.WindowsSupport; 038 import org.fusesource.jansi.internal.Kernel32.CONSOLE_SCREEN_BUFFER_INFO; 039 import org.fusesource.jansi.internal.Kernel32.COORD; 040 041 /** 042 * A Windows ANSI escape processor, uses JNA to access native platform 043 * API's to change the console attributes. 044 * 045 * @since 1.0 046 * @author <a href="http://hiramchirino.com">Hiram Chirino</a> 047 */ 048 public final class WindowsAnsiOutputStream extends AnsiOutputStream { 049 050 private static final long console = GetStdHandle(STD_OUTPUT_HANDLE); 051 052 private static final short FOREGROUND_BLACK = 0; 053 private static final short FOREGROUND_YELLOW = (short) (FOREGROUND_RED|FOREGROUND_GREEN); 054 private static final short FOREGROUND_MAGENTA = (short) (FOREGROUND_BLUE|FOREGROUND_RED); 055 private static final short FOREGROUND_CYAN = (short) (FOREGROUND_BLUE|FOREGROUND_GREEN); 056 private static final short FOREGROUND_WHITE = (short) (FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE); 057 058 private static final short BACKGROUND_BLACK = 0; 059 private static final short BACKGROUND_YELLOW = (short) (BACKGROUND_RED|BACKGROUND_GREEN); 060 private static final short BACKGROUND_MAGENTA = (short) (BACKGROUND_BLUE|BACKGROUND_RED); 061 private static final short BACKGROUND_CYAN = (short) (BACKGROUND_BLUE|BACKGROUND_GREEN); 062 private static final short BACKGROUND_WHITE = (short) (BACKGROUND_RED|BACKGROUND_GREEN|BACKGROUND_BLUE); 063 064 private static final short ANSI_FOREGROUND_COLOR_MAP[] = { 065 FOREGROUND_BLACK, 066 FOREGROUND_RED, 067 FOREGROUND_GREEN, 068 FOREGROUND_YELLOW, 069 FOREGROUND_BLUE, 070 FOREGROUND_MAGENTA, 071 FOREGROUND_CYAN, 072 FOREGROUND_WHITE, 073 }; 074 075 private static final short ANSI_BACKGROUND_COLOR_MAP[] = { 076 BACKGROUND_BLACK, 077 BACKGROUND_RED, 078 BACKGROUND_GREEN, 079 BACKGROUND_YELLOW, 080 BACKGROUND_BLUE, 081 BACKGROUND_MAGENTA, 082 BACKGROUND_CYAN, 083 BACKGROUND_WHITE, 084 }; 085 086 private final CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO(); 087 private final short originalColors; 088 089 private boolean negative; 090 private short savedX = -1; 091 private short savedY = -1; 092 093 public WindowsAnsiOutputStream(OutputStream os) throws IOException { 094 super(os); 095 getConsoleInfo(); 096 originalColors = info.attributes; 097 } 098 099 private void getConsoleInfo() throws IOException { 100 out.flush(); 101 if( GetConsoleScreenBufferInfo(console, info) == 0 ) { 102 throw new IOException("Could not get the screen info: "+WindowsSupport.getLastErrorMessage()); 103 } 104 if( negative ) { 105 info.attributes = invertAttributeColors(info.attributes); 106 } 107 } 108 109 private void applyAttribute() throws IOException { 110 out.flush(); 111 short attributes = info.attributes; 112 if( negative ) { 113 attributes = invertAttributeColors(attributes); 114 } 115 if( SetConsoleTextAttribute(console, attributes) == 0 ) { 116 throw new IOException(WindowsSupport.getLastErrorMessage()); 117 } 118 } 119 120 private short invertAttributeColors(short attibutes) { 121 // Swap the the Foreground and Background bits. 122 int fg = 0x000F & attibutes; 123 fg <<= 8; 124 int bg = 0X00F0 * attibutes; 125 bg >>=8; 126 attibutes = (short) ((attibutes & 0xFF00) | fg | bg); 127 return attibutes; 128 } 129 130 private void applyCursorPosition() throws IOException { 131 if( SetConsoleCursorPosition(console, info.cursorPosition.copy()) == 0 ) { 132 throw new IOException(WindowsSupport.getLastErrorMessage()); 133 } 134 } 135 136 @Override 137 protected void processEraseScreen(int eraseOption) throws IOException { 138 getConsoleInfo(); 139 int[] written = new int[1]; 140 switch(eraseOption) { 141 case ERASE_SCREEN: 142 COORD topLeft = new COORD(); 143 topLeft.x = 0; 144 topLeft.y = info.window.top; 145 int screenLength = info.window.height() * info.size.x; 146 FillConsoleOutputCharacterW(console, ' ', screenLength, topLeft, written); 147 break; 148 case ERASE_SCREEN_TO_BEGINING: 149 COORD topLeft2 = new COORD(); 150 topLeft2.x = 0; 151 topLeft2.y = info.window.top; 152 int lengthToCursor = (info.cursorPosition.y - info.window.top) * info.size.x 153 + info.cursorPosition.x; 154 FillConsoleOutputCharacterW(console, ' ', lengthToCursor, topLeft2, written); 155 break; 156 case ERASE_SCREEN_TO_END: 157 int lengthToEnd = (info.window.bottom - info.cursorPosition.y) * info.size.x + 158 (info.size.x - info.cursorPosition.x); 159 FillConsoleOutputCharacterW(console, ' ', lengthToEnd, info.cursorPosition.copy(), written); 160 } 161 } 162 163 @Override 164 protected void processEraseLine(int eraseOption) throws IOException { 165 getConsoleInfo(); 166 int[] written = new int[1]; 167 switch(eraseOption) { 168 case ERASE_LINE: 169 COORD leftColCurrRow = info.cursorPosition.copy(); 170 leftColCurrRow.x = 0; 171 FillConsoleOutputCharacterW(console, ' ', info.size.x, leftColCurrRow, written); 172 break; 173 case ERASE_LINE_TO_BEGINING: 174 COORD leftColCurrRow2 = info.cursorPosition.copy(); 175 leftColCurrRow2.x = 0; 176 FillConsoleOutputCharacterW(console, ' ', info.cursorPosition.x, leftColCurrRow2, written); 177 break; 178 case ERASE_LINE_TO_END: 179 int lengthToLastCol = info.size.x - info.cursorPosition.x; 180 FillConsoleOutputCharacterW(console, ' ', lengthToLastCol, info.cursorPosition.copy(), written); 181 } 182 } 183 184 @Override 185 protected void processCursorLeft(int count) throws IOException { 186 getConsoleInfo(); 187 info.cursorPosition.x = (short) Math.max(0, info.cursorPosition.x-count); 188 applyCursorPosition(); 189 } 190 191 @Override 192 protected void processCursorRight(int count) throws IOException { 193 getConsoleInfo(); 194 info.cursorPosition.x = (short)Math.min(info.window.width(), info.cursorPosition.x+count); 195 applyCursorPosition(); 196 } 197 198 @Override 199 protected void processCursorDown(int count) throws IOException { 200 getConsoleInfo(); 201 info.cursorPosition.y = (short) Math.min(info.size.y, info.cursorPosition.y+count); 202 applyCursorPosition(); 203 } 204 205 @Override 206 protected void processCursorUp(int count) throws IOException { 207 getConsoleInfo(); 208 info.cursorPosition.y = (short) Math.max(info.window.top, info.cursorPosition.y-count); 209 applyCursorPosition(); 210 } 211 212 @Override 213 protected void processCursorTo(int row, int col) throws IOException { 214 getConsoleInfo(); 215 info.cursorPosition.y = (short) Math.max(info.window.top, Math.min(info.size.y, info.window.top+row-1)); 216 info.cursorPosition.x = (short) Math.max(0, Math.min(info.window.width(), col-1)); 217 applyCursorPosition(); 218 } 219 220 @Override 221 protected void processCursorToColumn(int x) throws IOException { 222 getConsoleInfo(); 223 info.cursorPosition.x = (short) Math.max(0, Math.min(info.window.width(), x-1)); 224 applyCursorPosition(); 225 } 226 227 @Override 228 protected void processSetForegroundColor(int color) throws IOException { 229 info.attributes = (short)((info.attributes & ~0x0007 ) | ANSI_FOREGROUND_COLOR_MAP[color]); 230 applyAttribute(); 231 } 232 233 @Override 234 protected void processSetBackgroundColor(int color) throws IOException { 235 info.attributes = (short)((info.attributes & ~0x0070 ) | ANSI_BACKGROUND_COLOR_MAP[color]); 236 applyAttribute(); 237 } 238 239 @Override 240 protected void processAttributeRest() throws IOException { 241 info.attributes = (short)((info.attributes & ~0x00FF ) | originalColors); 242 this.negative = false; 243 applyAttribute(); 244 } 245 246 @Override 247 protected void processSetAttribute(int attribute) throws IOException { 248 switch(attribute) { 249 case ATTRIBUTE_INTENSITY_BOLD: 250 info.attributes = (short)(info.attributes | FOREGROUND_INTENSITY ); 251 applyAttribute(); 252 break; 253 case ATTRIBUTE_INTENSITY_NORMAL: 254 info.attributes = (short)(info.attributes & ~FOREGROUND_INTENSITY ); 255 applyAttribute(); 256 break; 257 258 // Yeah, setting the background intensity is not underlining.. but it's best we can do 259 // using the Windows console API 260 case ATTRIBUTE_UNDERLINE: 261 info.attributes = (short)(info.attributes | BACKGROUND_INTENSITY ); 262 applyAttribute(); 263 break; 264 case ATTRIBUTE_UNDERLINE_OFF: 265 info.attributes = (short)(info.attributes & ~BACKGROUND_INTENSITY ); 266 applyAttribute(); 267 break; 268 269 case ATTRIBUTE_NEGATIVE_ON: 270 negative = true; 271 applyAttribute(); 272 break; 273 case ATTRIBUTE_NEGATIVE_Off: 274 negative = false; 275 applyAttribute(); 276 break; 277 } 278 } 279 280 @Override 281 protected void processSaveCursorPosition() throws IOException { 282 getConsoleInfo(); 283 savedX = info.cursorPosition.x; 284 savedY = info.cursorPosition.y; 285 } 286 287 @Override 288 protected void processRestoreCursorPosition() throws IOException { 289 // restore only if there was a save operation first 290 if (savedX != -1 && savedY != -1) { 291 out.flush(); 292 info.cursorPosition.x = savedX; 293 info.cursorPosition.y = savedY; 294 applyCursorPosition(); 295 } 296 } 297 }