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    }