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 }