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_RED; 022 import static org.fusesource.jansi.internal.Kernel32.FOREGROUND_BLUE; 023 import static org.fusesource.jansi.internal.Kernel32.FOREGROUND_GREEN; 024 import static org.fusesource.jansi.internal.Kernel32.FOREGROUND_RED; 025 import static org.fusesource.jansi.internal.Kernel32.KERNEL32; 026 027 import java.io.IOException; 028 import java.io.OutputStream; 029 030 import org.fusesource.jansi.internal.Kernel32; 031 import org.fusesource.jansi.internal.WindowsSupport; 032 import org.fusesource.jansi.internal.Kernel32.CONSOLE_SCREEN_BUFFER_INFO; 033 import org.fusesource.jansi.internal.Kernel32.COORD; 034 035 import com.sun.jna.Pointer; 036 import com.sun.jna.ptr.IntByReference; 037 038 /** 039 * A Windows ANSI escape processor, uses JNA to access native platform 040 * API's to change the console attributes. 041 * 042 * @since 1.0 043 * @author <a href="http://hiramchirino.com">Hiram Chirino</a> 044 */ 045 public final class WindowsAnsiOutputStream extends AnsiOutputStream { 046 047 private static final Pointer console = KERNEL32.GetStdHandle(Kernel32.STD_OUTPUT_HANDLE); 048 049 private static final short FOREGROUND_BLACK = 0; 050 private static final short FOREGROUND_YELLOW = FOREGROUND_RED|FOREGROUND_GREEN; 051 private static final short FOREGROUND_MAGENTA = FOREGROUND_BLUE|FOREGROUND_RED; 052 private static final short FOREGROUND_CYAN = FOREGROUND_BLUE|FOREGROUND_GREEN; 053 private static final short FOREGROUND_WHITE = FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE; 054 055 private static final short BACKGROUND_BLACK = 0; 056 private static final short BACKGROUND_YELLOW = BACKGROUND_RED|BACKGROUND_GREEN; 057 private static final short BACKGROUND_MAGENTA = BACKGROUND_BLUE|BACKGROUND_RED; 058 private static final short BACKGROUND_CYAN = BACKGROUND_BLUE|BACKGROUND_GREEN; 059 private static final short BACKGROUND_WHITE = BACKGROUND_RED|BACKGROUND_GREEN|BACKGROUND_BLUE; 060 061 private static final short ANSI_FOREGROUND_COLOR_MAP[] = { 062 FOREGROUND_BLACK, 063 FOREGROUND_RED, 064 FOREGROUND_GREEN, 065 FOREGROUND_YELLOW, 066 FOREGROUND_BLUE, 067 FOREGROUND_MAGENTA, 068 FOREGROUND_CYAN, 069 FOREGROUND_WHITE, 070 }; 071 072 private static final short ANSI_BACKGROUND_COLOR_MAP[] = { 073 BACKGROUND_BLACK, 074 BACKGROUND_RED, 075 BACKGROUND_GREEN, 076 BACKGROUND_YELLOW, 077 BACKGROUND_BLUE, 078 BACKGROUND_MAGENTA, 079 BACKGROUND_CYAN, 080 BACKGROUND_WHITE, 081 }; 082 083 private final CONSOLE_SCREEN_BUFFER_INFO.ByReference info = new CONSOLE_SCREEN_BUFFER_INFO.ByReference(); 084 private final short originalColors; 085 086 private boolean negative; 087 088 public WindowsAnsiOutputStream(OutputStream os) throws IOException { 089 super(os); 090 getConsoleInfo(); 091 originalColors = info.attributes; 092 } 093 094 private void getConsoleInfo() throws IOException { 095 out.flush(); 096 if( KERNEL32.GetConsoleScreenBufferInfo(console, info) == 0 ) { 097 throw new IOException("Could not get the screen info: "+WindowsSupport.getLastErrorMessage()); 098 } 099 if( negative ) { 100 info.attributes = invertAttributeColors(info.attributes); 101 } 102 } 103 104 private void applyAttribute() throws IOException { 105 out.flush(); 106 short attributes = info.attributes; 107 if( negative ) { 108 attributes = invertAttributeColors(attributes); 109 } 110 if( KERNEL32.SetConsoleTextAttribute(console, attributes) == 0 ) { 111 throw new IOException(WindowsSupport.getLastErrorMessage()); 112 } 113 } 114 115 private short invertAttributeColors(short attibutes) { 116 // Swap the the Foreground and Background bits. 117 int fg = 0x000F & attibutes; 118 fg <<= 8; 119 int bg = 0X00F0 * attibutes; 120 bg >>=8; 121 attibutes = (short) ((attibutes & 0xFF00) | fg | bg); 122 return attibutes; 123 } 124 125 private void applyCursorPosition() throws IOException { 126 if( KERNEL32.SetConsoleCursorPosition(console, info.cursorPosition.copy()) == 0 ) { 127 throw new IOException(WindowsSupport.getLastErrorMessage()); 128 } 129 } 130 131 @Override 132 protected void processEraseScreen(int eraseOption) throws IOException { 133 getConsoleInfo(); 134 IntByReference written = new IntByReference(); 135 switch(eraseOption) { 136 case ERASE_SCREEN: 137 COORD.ByValue topLeft = new COORD.ByValue(); 138 topLeft.x = 0; 139 topLeft.y = info.window.top; 140 int screenLength = info.window.height() * info.size.x; 141 KERNEL32.FillConsoleOutputCharacterW(console, ' ', screenLength, topLeft, written); 142 break; 143 case ERASE_SCREEN_TO_BEGINING: 144 COORD.ByValue topLeft2 = new COORD.ByValue(); 145 topLeft2.x = 0; 146 topLeft2.y = info.window.top; 147 int lengthToCursor = (info.cursorPosition.y - info.window.top) * info.size.x 148 + info.cursorPosition.x; 149 KERNEL32.FillConsoleOutputCharacterW(console, ' ', lengthToCursor, topLeft2, written); 150 break; 151 case ERASE_SCREEN_TO_END: 152 int lengthToEnd = (info.window.bottom - info.cursorPosition.y) * info.size.x + 153 (info.size.x - info.cursorPosition.x); 154 KERNEL32.FillConsoleOutputCharacterW(console, ' ', lengthToEnd, info.cursorPosition.copy(), written); 155 } 156 } 157 158 @Override 159 protected void processEraseLine(int eraseOption) throws IOException { 160 getConsoleInfo(); 161 IntByReference written = new IntByReference(); 162 switch(eraseOption) { 163 case ERASE_LINE: 164 COORD.ByValue leftColCurrRow = info.cursorPosition.copy(); 165 leftColCurrRow.x = 0; 166 KERNEL32.FillConsoleOutputCharacterW(console, ' ', info.size.x, leftColCurrRow, written); 167 break; 168 case ERASE_LINE_TO_BEGINING: 169 COORD.ByValue leftColCurrRow2 = info.cursorPosition.copy(); 170 leftColCurrRow2.x = 0; 171 KERNEL32.FillConsoleOutputCharacterW(console, ' ', info.cursorPosition.x, leftColCurrRow2, written); 172 break; 173 case ERASE_LINE_TO_END: 174 int lengthToLastCol = info.size.x - info.cursorPosition.x; 175 KERNEL32.FillConsoleOutputCharacterW(console, ' ', lengthToLastCol, info.cursorPosition.copy(), written); 176 } 177 } 178 179 @Override 180 protected void processCursorLeft(int count) throws IOException { 181 getConsoleInfo(); 182 info.cursorPosition.x = (short) Math.max(0, info.cursorPosition.x-count); 183 applyCursorPosition(); 184 } 185 186 @Override 187 protected void processCursorRight(int count) throws IOException { 188 getConsoleInfo(); 189 info.cursorPosition.x = (short)Math.min(info.window.width(), info.cursorPosition.x+count); 190 applyCursorPosition(); 191 } 192 193 @Override 194 protected void processCursorDown(int count) throws IOException { 195 getConsoleInfo(); 196 info.cursorPosition.y = (short) Math.min(info.size.y, info.cursorPosition.y+count); 197 applyCursorPosition(); 198 } 199 200 @Override 201 protected void processCursorUp(int count) throws IOException { 202 getConsoleInfo(); 203 info.cursorPosition.y = (short) Math.max(info.window.top, info.cursorPosition.y-count); 204 applyCursorPosition(); 205 } 206 207 @Override 208 protected void processCursorTo(int x, int y) throws IOException { 209 getConsoleInfo(); 210 info.cursorPosition.y = (short) Math.max(info.window.top, Math.min(info.size.y, info.window.top+y-1)); 211 info.cursorPosition.x = (short) Math.max(0, Math.min(info.window.width(), x-1)); 212 applyCursorPosition(); 213 } 214 215 @Override 216 protected void processCursorToColumn(int x) throws IOException { 217 getConsoleInfo(); 218 info.cursorPosition.x = (short) Math.max(0, Math.min(info.window.width(), x-1)); 219 applyCursorPosition(); 220 } 221 222 @Override 223 protected void processSetForegroundColor(int color) throws IOException { 224 info.attributes = (short)((info.attributes & ~0x0007 ) | ANSI_FOREGROUND_COLOR_MAP[color]); 225 applyAttribute(); 226 } 227 228 @Override 229 protected void processSetBackgroundColor(int color) throws IOException { 230 info.attributes = (short)((info.attributes & ~0x0070 ) | ANSI_BACKGROUND_COLOR_MAP[color]); 231 applyAttribute(); 232 } 233 234 @Override 235 protected void processAttributeRest() throws IOException { 236 info.attributes = (short)((info.attributes & ~0x00FF ) | originalColors); 237 this.negative = false; 238 applyAttribute(); 239 } 240 241 @Override 242 protected void processSetAttribute(int attribute) throws IOException { 243 switch(attribute) { 244 case ATTRIBUTE_INTENSITY_BOLD: 245 info.attributes = (short)(info.attributes | Kernel32.FOREGROUND_INTENSITY ); 246 applyAttribute(); 247 break; 248 case ATTRIBUTE_INTENSITY_NORMAL: 249 info.attributes = (short)(info.attributes & ~Kernel32.FOREGROUND_INTENSITY ); 250 applyAttribute(); 251 break; 252 253 // Yeah, setting the background intensity is not underlining.. but it's best we can do 254 // using the Windows console API 255 case ATTRIBUTE_UNDERLINE: 256 info.attributes = (short)(info.attributes | Kernel32.BACKGROUND_INTENSITY ); 257 applyAttribute(); 258 break; 259 case ATTRIBUTE_UNDERLINE_OFF: 260 info.attributes = (short)(info.attributes & ~Kernel32.BACKGROUND_INTENSITY ); 261 applyAttribute(); 262 break; 263 264 case ATTRIBUTE_NEGATIVE_ON: 265 negative = true; 266 applyAttribute(); 267 break; 268 case ATTRIBUTE_NEGATIVE_Off: 269 negative = false; 270 applyAttribute(); 271 break; 272 } 273 } 274 }