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 java.io.FilterOutputStream; 019import java.io.IOException; 020import java.io.OutputStream; 021import java.nio.charset.Charset; 022import java.util.ArrayList; 023import java.util.Iterator; 024 025/** 026 * A ANSI output stream extracts ANSI escape codes written to 027 * an output stream and calls corresponding <code>process*</code> methods. 028 * 029 * For more information about ANSI escape codes, see: 030 * http://en.wikipedia.org/wiki/ANSI_escape_code 031 * 032 * This class just filters out the escape codes so that they are not 033 * sent out to the underlying OutputStream: <code>process*</code> methods 034 * are empty. Subclasses should actually perform the ANSI escape behaviors 035 * by implementing active code in <code>process*</code> methods. 036 * 037 * @author <a href="http://hiramchirino.com">Hiram Chirino</a> 038 * @author Joris Kuipers 039 * @since 1.0 040 */ 041public class AnsiOutputStream extends FilterOutputStream { 042 043 public static final byte[] RESET_CODE = "\033[0m".getBytes(); 044 045 @Deprecated 046 public static final byte[] REST_CODE = RESET_CODE; 047 048 public AnsiOutputStream(OutputStream os) { 049 super(os); 050 } 051 052 private final static int MAX_ESCAPE_SEQUENCE_LENGTH = 100; 053 private final byte[] buffer = new byte[MAX_ESCAPE_SEQUENCE_LENGTH]; 054 private int pos = 0; 055 private int startOfValue; 056 private final ArrayList<Object> options = new ArrayList<Object>(); 057 058 private static final int LOOKING_FOR_FIRST_ESC_CHAR = 0; 059 private static final int LOOKING_FOR_SECOND_ESC_CHAR = 1; 060 private static final int LOOKING_FOR_NEXT_ARG = 2; 061 private static final int LOOKING_FOR_STR_ARG_END = 3; 062 private static final int LOOKING_FOR_INT_ARG_END = 4; 063 private static final int LOOKING_FOR_OSC_COMMAND = 5; 064 private static final int LOOKING_FOR_OSC_COMMAND_END = 6; 065 private static final int LOOKING_FOR_OSC_PARAM = 7; 066 private static final int LOOKING_FOR_ST = 8; 067 068 int state = LOOKING_FOR_FIRST_ESC_CHAR; 069 070 private static final int FIRST_ESC_CHAR = 27; 071 private static final int SECOND_ESC_CHAR = '['; 072 private static final int SECOND_OSC_CHAR = ']'; 073 private static final int BEL = 7; 074 private static final int SECOND_ST_CHAR = '\\'; 075 076 @Override 077 public synchronized void write(int data) throws IOException { 078 switch (state) { 079 case LOOKING_FOR_FIRST_ESC_CHAR: 080 if (data == FIRST_ESC_CHAR) { 081 buffer[pos++] = (byte) data; 082 state = LOOKING_FOR_SECOND_ESC_CHAR; 083 } else { 084 out.write(data); 085 } 086 break; 087 088 case LOOKING_FOR_SECOND_ESC_CHAR: 089 buffer[pos++] = (byte) data; 090 if (data == SECOND_ESC_CHAR) { 091 state = LOOKING_FOR_NEXT_ARG; 092 } else if (data == SECOND_OSC_CHAR) { 093 state = LOOKING_FOR_OSC_COMMAND; 094 } else { 095 reset(false); 096 } 097 break; 098 099 case LOOKING_FOR_NEXT_ARG: 100 buffer[pos++] = (byte) data; 101 if ('"' == data) { 102 startOfValue = pos - 1; 103 state = LOOKING_FOR_STR_ARG_END; 104 } else if ('0' <= data && data <= '9') { 105 startOfValue = pos - 1; 106 state = LOOKING_FOR_INT_ARG_END; 107 } else if (';' == data) { 108 options.add(null); 109 } else if ('?' == data) { 110 options.add('?'); 111 } else if ('=' == data) { 112 options.add('='); 113 } else { 114 reset(processEscapeCommand(options, data)); 115 } 116 break; 117 default: 118 break; 119 120 case LOOKING_FOR_INT_ARG_END: 121 buffer[pos++] = (byte) data; 122 if (!('0' <= data && data <= '9')) { 123 String strValue = new String(buffer, startOfValue, (pos - 1) - startOfValue, Charset.defaultCharset()); 124 Integer value = new Integer(strValue); 125 options.add(value); 126 if (data == ';') { 127 state = LOOKING_FOR_NEXT_ARG; 128 } else { 129 reset(processEscapeCommand(options, data)); 130 } 131 } 132 break; 133 134 case LOOKING_FOR_STR_ARG_END: 135 buffer[pos++] = (byte) data; 136 if ('"' != data) { 137 String value = new String(buffer, startOfValue, (pos - 1) - startOfValue, Charset.defaultCharset()); 138 options.add(value); 139 if (data == ';') { 140 state = LOOKING_FOR_NEXT_ARG; 141 } else { 142 reset(processEscapeCommand(options, data)); 143 } 144 } 145 break; 146 147 case LOOKING_FOR_OSC_COMMAND: 148 buffer[pos++] = (byte) data; 149 if ('0' <= data && data <= '9') { 150 startOfValue = pos - 1; 151 state = LOOKING_FOR_OSC_COMMAND_END; 152 } else { 153 reset(false); 154 } 155 break; 156 157 case LOOKING_FOR_OSC_COMMAND_END: 158 buffer[pos++] = (byte) data; 159 if (';' == data) { 160 String strValue = new String(buffer, startOfValue, (pos - 1) - startOfValue, Charset.defaultCharset()); 161 Integer value = new Integer(strValue); 162 options.add(value); 163 startOfValue = pos; 164 state = LOOKING_FOR_OSC_PARAM; 165 } else if ('0' <= data && data <= '9') { 166 // already pushed digit to buffer, just keep looking 167 } else { 168 // oops, did not expect this 169 reset(false); 170 } 171 break; 172 173 case LOOKING_FOR_OSC_PARAM: 174 buffer[pos++] = (byte) data; 175 if (BEL == data) { 176 String value = new String(buffer, startOfValue, (pos - 1) - startOfValue, Charset.defaultCharset()); 177 options.add(value); 178 reset(processOperatingSystemCommand(options)); 179 } else if (FIRST_ESC_CHAR == data) { 180 state = LOOKING_FOR_ST; 181 } else { 182 // just keep looking while adding text 183 } 184 break; 185 186 case LOOKING_FOR_ST: 187 buffer[pos++] = (byte) data; 188 if (SECOND_ST_CHAR == data) { 189 String value = new String(buffer, startOfValue, (pos - 2) - startOfValue, Charset.defaultCharset()); 190 options.add(value); 191 reset(processOperatingSystemCommand(options)); 192 } else { 193 state = LOOKING_FOR_OSC_PARAM; 194 } 195 break; 196 } 197 198 // Is it just too long? 199 if (pos >= buffer.length) { 200 reset(false); 201 } 202 } 203 204 /** 205 * Resets all state to continue with regular parsing 206 * @param skipBuffer if current buffer should be skipped or written to out 207 * @throws IOException 208 */ 209 private void reset(boolean skipBuffer) throws IOException { 210 if (!skipBuffer) { 211 out.write(buffer, 0, pos); 212 } 213 pos = 0; 214 startOfValue = 0; 215 options.clear(); 216 state = LOOKING_FOR_FIRST_ESC_CHAR; 217 } 218 219 /** 220 * Helper for processEscapeCommand() to iterate over integer options 221 * @param optionsIterator the underlying iterator 222 * @throws IOException if no more non-null values left 223 */ 224 private int getNextOptionInt(Iterator<Object> optionsIterator) throws IOException { 225 for (;;) { 226 if (!optionsIterator.hasNext()) 227 throw new IllegalArgumentException(); 228 Object arg = optionsIterator.next(); 229 if (arg != null) 230 return (Integer) arg; 231 } 232 } 233 234 /** 235 * 236 * @param options 237 * @param command 238 * @return true if the escape command was processed. 239 */ 240 private boolean processEscapeCommand(ArrayList<Object> options, int command) throws IOException { 241 try { 242 switch (command) { 243 case 'A': 244 processCursorUp(optionInt(options, 0, 1)); 245 return true; 246 case 'B': 247 processCursorDown(optionInt(options, 0, 1)); 248 return true; 249 case 'C': 250 processCursorRight(optionInt(options, 0, 1)); 251 return true; 252 case 'D': 253 processCursorLeft(optionInt(options, 0, 1)); 254 return true; 255 case 'E': 256 processCursorDownLine(optionInt(options, 0, 1)); 257 return true; 258 case 'F': 259 processCursorUpLine(optionInt(options, 0, 1)); 260 return true; 261 case 'G': 262 processCursorToColumn(optionInt(options, 0)); 263 return true; 264 case 'H': 265 case 'f': 266 processCursorTo(optionInt(options, 0, 1), optionInt(options, 1, 1)); 267 return true; 268 case 'J': 269 processEraseScreen(optionInt(options, 0, 0)); 270 return true; 271 case 'K': 272 processEraseLine(optionInt(options, 0, 0)); 273 return true; 274 case 'L': 275 processInsertLine(optionInt(options, 0, 1)); 276 return true; 277 case 'M': 278 processDeleteLine(optionInt(options, 0, 1)); 279 return true; 280 case 'S': 281 processScrollUp(optionInt(options, 0, 1)); 282 return true; 283 case 'T': 284 processScrollDown(optionInt(options, 0, 1)); 285 return true; 286 case 'm': 287 // Validate all options are ints... 288 for (Object next : options) { 289 if (next != null && next.getClass() != Integer.class) { 290 throw new IllegalArgumentException(); 291 } 292 } 293 294 int count = 0; 295 Iterator<Object> optionsIterator = options.iterator(); 296 while (optionsIterator.hasNext()) { 297 Object next = optionsIterator.next(); 298 if (next != null) { 299 count++; 300 int value = (Integer) next; 301 if (30 <= value && value <= 37) { 302 processSetForegroundColor(value - 30); 303 } else if (40 <= value && value <= 47) { 304 processSetBackgroundColor(value - 40); 305 } else if (90 <= value && value <= 97) { 306 processSetForegroundColor(value - 90, true); 307 } else if (100 <= value && value <= 107) { 308 processSetBackgroundColor(value - 100, true); 309 } else if (value == 38 || value == 48) { 310 // extended color like `esc[38;5;<index>m` or `esc[38;2;<r>;<g>;<b>m` 311 int arg2or5 = getNextOptionInt(optionsIterator); 312 if (arg2or5 == 2) { 313 // 24 bit color style like `esc[38;2;<r>;<g>;<b>m` 314 int r = getNextOptionInt(optionsIterator); 315 int g = getNextOptionInt(optionsIterator); 316 int b = getNextOptionInt(optionsIterator); 317 if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) { 318 if (value == 38) 319 processSetForegroundColorExt(r, g, b); 320 else 321 processSetBackgroundColorExt(r, g, b); 322 } else { 323 throw new IllegalArgumentException(); 324 } 325 } 326 else if (arg2or5 == 5) { 327 // 256 color style like `esc[38;5;<index>m` 328 int paletteIndex = getNextOptionInt(optionsIterator); 329 if (paletteIndex >= 0 && paletteIndex <= 255) { 330 if (value == 38) 331 processSetForegroundColorExt(paletteIndex); 332 else 333 processSetBackgroundColorExt(paletteIndex); 334 } else { 335 throw new IllegalArgumentException(); 336 } 337 } 338 else { 339 throw new IllegalArgumentException(); 340 } 341 } else { 342 switch (value) { 343 case 39: 344 processDefaultTextColor(); 345 break; 346 case 49: 347 processDefaultBackgroundColor(); 348 break; 349 case 0: 350 processAttributeRest(); 351 break; 352 default: 353 processSetAttribute(value); 354 } 355 } 356 } 357 } 358 if (count == 0) { 359 processAttributeRest(); 360 } 361 return true; 362 case 's': 363 processSaveCursorPosition(); 364 return true; 365 case 'u': 366 processRestoreCursorPosition(); 367 return true; 368 369 default: 370 if ('a' <= command && 'z' <= command) { 371 processUnknownExtension(options, command); 372 return true; 373 } 374 if ('A' <= command && 'Z' <= command) { 375 processUnknownExtension(options, command); 376 return true; 377 } 378 return false; 379 } 380 } catch (IllegalArgumentException ignore) { 381 } 382 return false; 383 } 384 385 /** 386 * 387 * @param options 388 * @return true if the operating system command was processed. 389 */ 390 private boolean processOperatingSystemCommand(ArrayList<Object> options) throws IOException { 391 int command = optionInt(options, 0); 392 String label = (String) options.get(1); 393 // for command > 2 label could be composed (i.e. contain ';'), but we'll leave 394 // it to processUnknownOperatingSystemCommand implementations to handle that 395 try { 396 switch (command) { 397 case 0: 398 processChangeIconNameAndWindowTitle(label); 399 return true; 400 case 1: 401 processChangeIconName(label); 402 return true; 403 case 2: 404 processChangeWindowTitle(label); 405 return true; 406 407 default: 408 // not exactly unknown, but not supported through dedicated process methods: 409 processUnknownOperatingSystemCommand(command, label); 410 return true; 411 } 412 } catch (IllegalArgumentException ignore) { 413 } 414 return false; 415 } 416 417 /** 418 * Process <code>CSI u</code> ANSI code, corresponding to <code>RCP – Restore Cursor Position</code> 419 * @throws IOException 420 */ 421 protected void processRestoreCursorPosition() throws IOException { 422 } 423 424 /** 425 * Process <code>CSI s</code> ANSI code, corresponding to <code>SCP – Save Cursor Position</code> 426 * @throws IOException 427 */ 428 protected void processSaveCursorPosition() throws IOException { 429 } 430 431 /** 432 * Process <code>CSI s</code> ANSI code, corresponding to <code>IL – Insert Line</code> 433 * @throws IOException 434 */ 435 protected void processInsertLine(int optionInt) throws IOException { 436 } 437 438 /** 439 * Process <code>CSI s</code> ANSI code, corresponding to <code>DL – Delete Line</code> 440 * @throws IOException 441 */ 442 protected void processDeleteLine(int optionInt) throws IOException { 443 } 444 445 /** 446 * Process <code>CSI n T</code> ANSI code, corresponding to <code>SD – Scroll Down</code> 447 * @throws IOException 448 */ 449 protected void processScrollDown(int optionInt) throws IOException { 450 } 451 452 /** 453 * Process <code>CSI n U</code> ANSI code, corresponding to <code>SU – Scroll Up</code> 454 * @throws IOException 455 */ 456 protected void processScrollUp(int optionInt) throws IOException { 457 } 458 459 protected static final int ERASE_SCREEN_TO_END = 0; 460 protected static final int ERASE_SCREEN_TO_BEGINING = 1; 461 protected static final int ERASE_SCREEN = 2; 462 463 /** 464 * Process <code>CSI n J</code> ANSI code, corresponding to <code>ED – Erase in Display</code> 465 * @throws IOException 466 */ 467 protected void processEraseScreen(int eraseOption) throws IOException { 468 } 469 470 protected static final int ERASE_LINE_TO_END = 0; 471 protected static final int ERASE_LINE_TO_BEGINING = 1; 472 protected static final int ERASE_LINE = 2; 473 474 /** 475 * Process <code>CSI n K</code> ANSI code, corresponding to <code>ED – Erase in Line</code> 476 * @throws IOException 477 */ 478 protected void processEraseLine(int eraseOption) throws IOException { 479 } 480 481 protected static final int ATTRIBUTE_INTENSITY_BOLD = 1; // Intensity: Bold 482 protected static final int ATTRIBUTE_INTENSITY_FAINT = 2; // Intensity; Faint not widely supported 483 protected static final int ATTRIBUTE_ITALIC = 3; // Italic; on not widely supported. Sometimes treated as inverse. 484 protected static final int ATTRIBUTE_UNDERLINE = 4; // Underline; Single 485 protected static final int ATTRIBUTE_BLINK_SLOW = 5; // Blink; Slow less than 150 per minute 486 protected static final int ATTRIBUTE_BLINK_FAST = 6; // Blink; Rapid MS-DOS ANSI.SYS; 150 per minute or more 487 protected static final int ATTRIBUTE_NEGATIVE_ON = 7; // Image; Negative inverse or reverse; swap foreground and background 488 protected static final int ATTRIBUTE_CONCEAL_ON = 8; // Conceal on 489 protected static final int ATTRIBUTE_UNDERLINE_DOUBLE = 21; // Underline; Double not widely supported 490 protected static final int ATTRIBUTE_INTENSITY_NORMAL = 22; // Intensity; Normal not bold and not faint 491 protected static final int ATTRIBUTE_UNDERLINE_OFF = 24; // Underline; None 492 protected static final int ATTRIBUTE_BLINK_OFF = 25; // Blink; off 493 @Deprecated 494 protected static final int ATTRIBUTE_NEGATIVE_Off = 27; // Image; Positive 495 protected static final int ATTRIBUTE_NEGATIVE_OFF = 27; // Image; Positive 496 protected static final int ATTRIBUTE_CONCEAL_OFF = 28; // Reveal conceal off 497 498 /** 499 * process <code>SGR</code> other than <code>0</code> (reset), <code>30-39</code> (foreground), 500 * <code>40-49</code> (background), <code>90-97</code> (foreground high intensity) or 501 * <code>100-107</code> (background high intensity) 502 * @param attribute 503 * @throws IOException 504 * @see #processAttributeRest() 505 * @see #processSetForegroundColor(int) 506 * @see #processSetForegroundColor(int, boolean) 507 * @see #processSetForegroundColorExt(int) 508 * @see #processSetForegroundColorExt(int, int, int) 509 * @see #processDefaultTextColor() 510 * @see #processDefaultBackgroundColor() 511 */ 512 protected void processSetAttribute(int attribute) throws IOException { 513 } 514 515 protected static final int BLACK = 0; 516 protected static final int RED = 1; 517 protected static final int GREEN = 2; 518 protected static final int YELLOW = 3; 519 protected static final int BLUE = 4; 520 protected static final int MAGENTA = 5; 521 protected static final int CYAN = 6; 522 protected static final int WHITE = 7; 523 524 /** 525 * process <code>SGR 30-37</code> corresponding to <code>Set text color (foreground)</code>. 526 * @param color the text color 527 * @throws IOException 528 */ 529 protected void processSetForegroundColor(int color) throws IOException { 530 processSetForegroundColor(color, false); 531 } 532 533 /** 534 * process <code>SGR 30-37</code> or <code>SGR 90-97</code> corresponding to 535 * <code>Set text color (foreground)</code> either in normal mode or high intensity. 536 * @param color the text color 537 * @param bright is high intensity? 538 * @throws IOException 539 */ 540 protected void processSetForegroundColor(int color, boolean bright) throws IOException { 541 } 542 543 /** 544 * process <code>SGR 38</code> corresponding to <code>extended set text color (foreground)</code> 545 * with a palette of 255 colors. 546 * @param paletteIndex the text color in the palette 547 * @throws IOException 548 */ 549 protected void processSetForegroundColorExt(int paletteIndex) throws IOException { 550 } 551 552 /** 553 * process <code>SGR 38</code> corresponding to <code>extended set text color (foreground)</code> 554 * with a 24 bits RGB definition of the color. 555 * @param r red 556 * @param g green 557 * @param b blue 558 * @throws IOException 559 */ 560 protected void processSetForegroundColorExt(int r, int g, int b) throws IOException { 561 } 562 563 /** 564 * process <code>SGR 40-47</code> corresponding to <code>Set background color</code>. 565 * @param color the background color 566 * @throws IOException 567 */ 568 protected void processSetBackgroundColor(int color) throws IOException { 569 processSetBackgroundColor(color, false); 570 } 571 572 /** 573 * process <code>SGR 40-47</code> or <code>SGR 100-107</code> corresponding to 574 * <code>Set background color</code> either in normal mode or high intensity. 575 * @param color the background color 576 * @param bright is high intensity? 577 * @throws IOException 578 */ 579 protected void processSetBackgroundColor(int color, boolean bright) throws IOException { 580 } 581 582 /** 583 * process <code>SGR 48</code> corresponding to <code>extended set background color</code> 584 * with a palette of 255 colors. 585 * @param paletteIndex the background color in the palette 586 * @throws IOException 587 */ 588 protected void processSetBackgroundColorExt(int paletteIndex) throws IOException { 589 } 590 591 /** 592 * process <code>SGR 48</code> corresponding to <code>extended set background color</code> 593 * with a 24 bits RGB definition of the color. 594 * @param r red 595 * @param g green 596 * @param b blue 597 * @throws IOException 598 */ 599 protected void processSetBackgroundColorExt(int r, int g, int b) throws IOException { 600 } 601 602 /** 603 * process <code>SGR 39</code> corresponding to <code>Default text color (foreground)</code> 604 * @throws IOException 605 */ 606 protected void processDefaultTextColor() throws IOException { 607 } 608 609 /** 610 * process <code>SGR 49</code> corresponding to <code>Default background color</code> 611 * @throws IOException 612 */ 613 protected void processDefaultBackgroundColor() throws IOException { 614 } 615 616 /** 617 * process <code>SGR 0</code> corresponding to <code>Reset / Normal</code> 618 * @throws IOException 619 */ 620 protected void processAttributeRest() throws IOException { 621 } 622 623 /** 624 * process <code>CSI n ; m H</code> corresponding to <code>CUP – Cursor Position</code> or 625 * <code>CSI n ; m f</code> corresponding to <code>HVP – Horizontal and Vertical Position</code> 626 * @param row 627 * @param col 628 * @throws IOException 629 */ 630 protected void processCursorTo(int row, int col) throws IOException { 631 } 632 633 /** 634 * process <code>CSI n G</code> corresponding to <code>CHA – Cursor Horizontal Absolute</code> 635 * @param x the column 636 * @throws IOException 637 */ 638 protected void processCursorToColumn(int x) throws IOException { 639 } 640 641 /** 642 * process <code>CSI n F</code> corresponding to <code>CPL – Cursor Previous Line</code> 643 * @param count line count 644 * @throws IOException 645 */ 646 protected void processCursorUpLine(int count) throws IOException { 647 } 648 649 /** 650 * process <code>CSI n E</code> corresponding to <code>CNL – Cursor Next Line</code> 651 * @param count line count 652 * @throws IOException 653 */ 654 protected void processCursorDownLine(int count) throws IOException { 655 // Poor mans impl.. 656 for (int i = 0; i < count; i++) { 657 out.write('\n'); 658 } 659 } 660 661 /** 662 * process <code>CSI n D</code> corresponding to <code>CUB – Cursor Back</code> 663 * @param count 664 * @throws IOException 665 */ 666 protected void processCursorLeft(int count) throws IOException { 667 } 668 669 /** 670 * process <code>CSI n C</code> corresponding to <code>CUF – Cursor Forward</code> 671 * @param count 672 * @throws IOException 673 */ 674 protected void processCursorRight(int count) throws IOException { 675 // Poor mans impl.. 676 for (int i = 0; i < count; i++) { 677 out.write(' '); 678 } 679 } 680 681 /** 682 * process <code>CSI n B</code> corresponding to <code>CUD – Cursor Down</code> 683 * @param count 684 * @throws IOException 685 */ 686 protected void processCursorDown(int count) throws IOException { 687 } 688 689 /** 690 * process <code>CSI n A</code> corresponding to <code>CUU – Cursor Up</code> 691 * @param count 692 * @throws IOException 693 */ 694 protected void processCursorUp(int count) throws IOException { 695 } 696 697 protected void processUnknownExtension(ArrayList<Object> options, int command) { 698 } 699 700 /** 701 * process <code>OSC 0;text BEL</code> corresponding to <code>Change Window and Icon label</code> 702 * @param label 703 * @throws IOException 704 */ 705 protected void processChangeIconNameAndWindowTitle(String label) { 706 processChangeIconName(label); 707 processChangeWindowTitle(label); 708 } 709 710 /** 711 * process <code>OSC 1;text BEL</code> corresponding to <code>Change Icon label</code> 712 * @param label 713 * @throws IOException 714 */ 715 protected void processChangeIconName(String label) { 716 } 717 718 /** 719 * process <code>OSC 2;text BEL</code> corresponding to <code>Change Window title</code> 720 * @param label 721 * @throws IOException 722 */ 723 protected void processChangeWindowTitle(String label) { 724 } 725 726 /** 727 * Process unknown <code>OSC</code> command. 728 * @param command 729 * @param param 730 */ 731 protected void processUnknownOperatingSystemCommand(int command, String param) { 732 } 733 734 private int optionInt(ArrayList<Object> options, int index) { 735 if (options.size() <= index) 736 throw new IllegalArgumentException(); 737 Object value = options.get(index); 738 if (value == null) 739 throw new IllegalArgumentException(); 740 if (!value.getClass().equals(Integer.class)) 741 throw new IllegalArgumentException(); 742 return (Integer) value; 743 } 744 745 private int optionInt(ArrayList<Object> options, int index, int defaultValue) { 746 if (options.size() > index) { 747 Object value = options.get(index); 748 if (value == null) { 749 return defaultValue; 750 } 751 return (Integer) value; 752 } 753 return defaultValue; 754 } 755 756 @Override 757 public void close() throws IOException { 758 write(RESET_CODE); 759 flush(); 760 super.close(); 761 } 762 763}