X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fjexer%2Fio%2FECMA48Terminal.java;h=d567a429c96bd4c60528680098e3466e1aab329b;hb=2ce6dab2bbd951e6d0f09f94759efda5ee4b65ac;hp=2ebdd36690d67432fa090e3a1d830b48c54edd6c;hpb=ea91242c4516c212e07f06c1a7d44ab912018e36;p=fanfix.git diff --git a/src/jexer/io/ECMA48Terminal.java b/src/jexer/io/ECMA48Terminal.java index 2ebdd36..d567a42 100644 --- a/src/jexer/io/ECMA48Terminal.java +++ b/src/jexer/io/ECMA48Terminal.java @@ -1,29 +1,27 @@ -/** +/* * Jexer - Java Text User Interface * - * License: LGPLv3 or later - * - * This module is licensed under the GNU Lesser General Public License - * Version 3. Please see the file "COPYING" in this directory for more - * information about the GNU Lesser General Public License Version 3. + * The MIT License (MIT) * - * Copyright (C) 2015 Kevin Lamonte + * Copyright (C) 2016 Kevin Lamonte * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * as published by the Free Software Foundation; either version 3 of - * the License, or (at your option) any later version. + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * You should have received a copy of the GNU Lesser General Public - * License along with this program; if not, see - * http://www.gnu.org/licenses/, or write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. * * @author Kevin Lamonte [kevin.lamonte@gmail.com] * @version 1 @@ -106,7 +104,6 @@ public final class ECMA48Terminal implements Runnable { ESCAPE_INTERMEDIATE, CSI_ENTRY, CSI_PARAM, - // CSI_INTERMEDIATE, MOUSE, MOUSE_SGR, } @@ -344,6 +341,95 @@ public final class ECMA48Terminal implements Runnable { readerThread.start(); } + /** + * Constructor sets up state for getEvent(). + * + * @param listener the object this backend needs to wake up when new + * input comes in + * @param input the InputStream underlying 'reader'. Its available() + * method is used to determine if reader.read() will block or not. + * @param reader a Reader connected to the remote user. + * @param writer a PrintWriter connected to the remote user. + * @param setRawMode if true, set System.in into raw mode with stty. + * This should in general not be used. It is here solely for Demo3, + * which uses System.in. + * @throws IllegalArgumentException if input, reader, or writer are null. + */ + public ECMA48Terminal(final Object listener, final InputStream input, + final Reader reader, final PrintWriter writer, + final boolean setRawMode) { + + if (input == null) { + throw new IllegalArgumentException("InputStream must be specified"); + } + if (reader == null) { + throw new IllegalArgumentException("Reader must be specified"); + } + if (writer == null) { + throw new IllegalArgumentException("Writer must be specified"); + } + reset(); + mouse1 = false; + mouse2 = false; + mouse3 = false; + stopReaderThread = false; + this.listener = listener; + + inputStream = input; + this.input = reader; + + if (setRawMode == true) { + sttyRaw(); + } + this.setRawMode = setRawMode; + + if (input instanceof SessionInfo) { + // This is a TelnetInputStream that exposes window size and + // environment variables from the telnet layer. + sessionInfo = (SessionInfo) input; + } + if (sessionInfo == null) { + if (setRawMode == true) { + // Reading right off the tty + sessionInfo = new TTYSessionInfo(); + } else { + sessionInfo = new TSessionInfo(); + } + } + + this.output = writer; + + // Enable mouse reporting and metaSendsEscape + this.output.printf("%s%s", mouse(true), xtermMetaSendsEscape(true)); + this.output.flush(); + + // Hang onto the window size + windowResize = new TResizeEvent(TResizeEvent.Type.SCREEN, + sessionInfo.getWindowWidth(), sessionInfo.getWindowHeight()); + + // Spin up the input reader + eventQueue = new LinkedList(); + readerThread = new Thread(this); + readerThread.start(); + } + + /** + * Constructor sets up state for getEvent(). + * + * @param listener the object this backend needs to wake up when new + * input comes in + * @param input the InputStream underlying 'reader'. Its available() + * method is used to determine if reader.read() will block or not. + * @param reader a Reader connected to the remote user. + * @param writer a PrintWriter connected to the remote user. + * @throws IllegalArgumentException if input, reader, or writer are null. + */ + public ECMA48Terminal(final Object listener, final InputStream input, + final Reader reader, final PrintWriter writer) { + + this(listener, input, reader, writer, false); + } + /** * Restore terminal to normal state. */ @@ -444,36 +530,16 @@ public final class ECMA48Terminal implements Runnable { */ private TInputEvent csiFnKey() { int key = 0; - int modifier = 0; if (params.size() > 0) { key = Integer.parseInt(params.get(0)); } - if (params.size() > 1) { - modifier = Integer.parseInt(params.get(1)); - } boolean alt = false; boolean ctrl = false; boolean shift = false; - - switch (modifier) { - case 0: - // No modifier - break; - case 2: - // Shift - shift = true; - break; - case 3: - // Alt - alt = true; - break; - case 5: - // Ctrl - ctrl = true; - break; - default: - // Unknown modifier, bail out - return null; + if (params.size() > 1) { + shift = csiIsShift(params.get(1)); + alt = csiIsAlt(params.get(1)); + ctrl = csiIsCtrl(params.get(1)); } switch (key) { @@ -779,6 +845,51 @@ public final class ECMA48Terminal implements Runnable { } } + /** + * Returns true if the CSI parameter for a keyboard command means that + * shift was down. + */ + private boolean csiIsShift(final String x) { + if ((x.equals("2")) + || (x.equals("4")) + || (x.equals("6")) + || (x.equals("8")) + ) { + return true; + } + return false; + } + + /** + * Returns true if the CSI parameter for a keyboard command means that + * alt was down. + */ + private boolean csiIsAlt(final String x) { + if ((x.equals("3")) + || (x.equals("4")) + || (x.equals("7")) + || (x.equals("8")) + ) { + return true; + } + return false; + } + + /** + * Returns true if the CSI parameter for a keyboard command means that + * ctrl was down. + */ + private boolean csiIsCtrl(final String x) { + if ((x.equals("5")) + || (x.equals("6")) + || (x.equals("7")) + || (x.equals("8")) + ) { + return true; + } + return false; + } + /** * Parses the next character of input to see if an InputEvent is * fully here. @@ -906,65 +1017,21 @@ public final class ECMA48Terminal implements Runnable { switch (ch) { case 'A': // Up - if (params.size() > 1) { - if (params.get(1).equals("2")) { - shift = true; - } - if (params.get(1).equals("5")) { - ctrl = true; - } - if (params.get(1).equals("3")) { - alt = true; - } - } events.add(new TKeypressEvent(kbUp, alt, ctrl, shift)); reset(); return; case 'B': // Down - if (params.size() > 1) { - if (params.get(1).equals("2")) { - shift = true; - } - if (params.get(1).equals("5")) { - ctrl = true; - } - if (params.get(1).equals("3")) { - alt = true; - } - } events.add(new TKeypressEvent(kbDown, alt, ctrl, shift)); reset(); return; case 'C': // Right - if (params.size() > 1) { - if (params.get(1).equals("2")) { - shift = true; - } - if (params.get(1).equals("5")) { - ctrl = true; - } - if (params.get(1).equals("3")) { - alt = true; - } - } events.add(new TKeypressEvent(kbRight, alt, ctrl, shift)); reset(); return; case 'D': // Left - if (params.size() > 1) { - if (params.get(1).equals("2")) { - shift = true; - } - if (params.get(1).equals("5")) { - ctrl = true; - } - if (params.get(1).equals("3")) { - alt = true; - } - } events.add(new TKeypressEvent(kbLeft, alt, ctrl, shift)); reset(); return; @@ -1063,15 +1130,9 @@ public final class ECMA48Terminal implements Runnable { case 'A': // Up if (params.size() > 1) { - if (params.get(1).equals("2")) { - shift = true; - } - if (params.get(1).equals("5")) { - ctrl = true; - } - if (params.get(1).equals("3")) { - alt = true; - } + shift = csiIsShift(params.get(1)); + alt = csiIsAlt(params.get(1)); + ctrl = csiIsCtrl(params.get(1)); } events.add(new TKeypressEvent(kbUp, alt, ctrl, shift)); reset(); @@ -1079,15 +1140,9 @@ public final class ECMA48Terminal implements Runnable { case 'B': // Down if (params.size() > 1) { - if (params.get(1).equals("2")) { - shift = true; - } - if (params.get(1).equals("5")) { - ctrl = true; - } - if (params.get(1).equals("3")) { - alt = true; - } + shift = csiIsShift(params.get(1)); + alt = csiIsAlt(params.get(1)); + ctrl = csiIsCtrl(params.get(1)); } events.add(new TKeypressEvent(kbDown, alt, ctrl, shift)); reset(); @@ -1095,15 +1150,9 @@ public final class ECMA48Terminal implements Runnable { case 'C': // Right if (params.size() > 1) { - if (params.get(1).equals("2")) { - shift = true; - } - if (params.get(1).equals("5")) { - ctrl = true; - } - if (params.get(1).equals("3")) { - alt = true; - } + shift = csiIsShift(params.get(1)); + alt = csiIsAlt(params.get(1)); + ctrl = csiIsCtrl(params.get(1)); } events.add(new TKeypressEvent(kbRight, alt, ctrl, shift)); reset(); @@ -1111,19 +1160,33 @@ public final class ECMA48Terminal implements Runnable { case 'D': // Left if (params.size() > 1) { - if (params.get(1).equals("2")) { - shift = true; - } - if (params.get(1).equals("5")) { - ctrl = true; - } - if (params.get(1).equals("3")) { - alt = true; - } + shift = csiIsShift(params.get(1)); + alt = csiIsAlt(params.get(1)); + ctrl = csiIsCtrl(params.get(1)); } events.add(new TKeypressEvent(kbLeft, alt, ctrl, shift)); reset(); return; + case 'H': + // Home + if (params.size() > 1) { + shift = csiIsShift(params.get(1)); + alt = csiIsAlt(params.get(1)); + ctrl = csiIsCtrl(params.get(1)); + } + events.add(new TKeypressEvent(kbHome, alt, ctrl, shift)); + reset(); + return; + case 'F': + // End + if (params.size() > 1) { + shift = csiIsShift(params.get(1)); + alt = csiIsAlt(params.get(1)); + ctrl = csiIsCtrl(params.get(1)); + } + events.add(new TKeypressEvent(kbEnd, alt, ctrl, shift)); + reset(); + return; default: break; } @@ -1166,22 +1229,14 @@ public final class ECMA48Terminal implements Runnable { } /** - * Convert a list of SGR parameters into a full escape sequence. This - * also eliminates a trailing ';' which would otherwise reset everything - * to white-on-black not-bold. + * Create an xterm OSC sequence to change the window title. Note package + * private access. * - * @param str string of parameters, e.g. "31;1;" - * @return the string to emit to an ANSI / ECMA-style terminal, - * e.g. "\033[31;1m" + * @param title the new title + * @return the string to emit to xterm */ - private String addHeaderSGR(String str) { - if (str.length() > 0) { - // Nix any trailing ';' because that resets all attributes - while (str.endsWith(":")) { - str = str.substring(0, str.length() - 1); - } - } - return "\033[" + str + "m"; + String setTitle(final String title) { + return "\033]2;" + title + "\007"; } /** @@ -1332,20 +1387,6 @@ public final class ECMA48Terminal implements Runnable { return sb.toString(); } - /** - * Create a SGR parameter sequence for enabling reverse color. - * - * @param on if true, turn on reverse - * @return the string to emit to an ANSI / ECMA-style terminal, - * e.g. "\033[7m" - */ - private String reverse(final boolean on) { - if (on) { - return "\033[7m"; - } - return "\033[27m"; - } - /** * Create a SGR parameter sequence to reset to defaults. Note package * private access. @@ -1372,87 +1413,6 @@ public final class ECMA48Terminal implements Runnable { return "0;37;40"; } - /** - * Create a SGR parameter sequence for enabling boldface. - * - * @param on if true, turn on bold - * @return the string to emit to an ANSI / ECMA-style terminal, - * e.g. "\033[1m" - */ - private String bold(final boolean on) { - return bold(on, true); - } - - /** - * Create a SGR parameter sequence for enabling boldface. - * - * @param on if true, turn on bold - * @param header if true, make the full header, otherwise just emit the - * bare parameter e.g. "1;" - * @return the string to emit to an ANSI / ECMA-style terminal, - * e.g. "\033[1m" - */ - private String bold(final boolean on, final boolean header) { - if (header) { - if (on) { - return "\033[1m"; - } - return "\033[22m"; - } - if (on) { - return "1;"; - } - return "22;"; - } - - /** - * Create a SGR parameter sequence for enabling blinking text. - * - * @param on if true, turn on blink - * @return the string to emit to an ANSI / ECMA-style terminal, - * e.g. "\033[5m" - */ - private String blink(final boolean on) { - return blink(on, true); - } - - /** - * Create a SGR parameter sequence for enabling blinking text. - * - * @param on if true, turn on blink - * @param header if true, make the full header, otherwise just emit the - * bare parameter e.g. "5;" - * @return the string to emit to an ANSI / ECMA-style terminal, - * e.g. "\033[5m" - */ - private String blink(final boolean on, final boolean header) { - if (header) { - if (on) { - return "\033[5m"; - } - return "\033[25m"; - } - if (on) { - return "5;"; - } - return "25;"; - } - - /** - * Create a SGR parameter sequence for enabling underline / underscored - * text. - * - * @param on if true, turn on underline - * @return the string to emit to an ANSI / ECMA-style terminal, - * e.g. "\033[4m" - */ - private String underline(final boolean on) { - if (on) { - return "\033[4m"; - } - return "\033[24m"; - } - /** * Create a SGR parameter sequence for enabling the visible cursor. Note * package private access. @@ -1493,35 +1453,6 @@ public final class ECMA48Terminal implements Runnable { return "\033[0;37;40m\033[K"; } - /** - * Clear the line up the cursor (inclusive). Because some terminals use - * back-color-erase, set the color to white-on-black beforehand. - * - * @return the string to emit to an ANSI / ECMA-style terminal - */ - private String clearPreceedingLine() { - return "\033[0;37;40m\033[1K"; - } - - /** - * Clear the line. Because some terminals use back-color-erase, set the - * color to white-on-black beforehand. - * - * @return the string to emit to an ANSI / ECMA-style terminal - */ - private String clearLine() { - return "\033[0;37;40m\033[2K"; - } - - /** - * Move the cursor to the top-left corner. - * - * @return the string to emit to an ANSI / ECMA-style terminal - */ - private String home() { - return "\033[H"; - } - /** * Move the cursor to (x, y). Note package private access. * @@ -1549,9 +1480,9 @@ public final class ECMA48Terminal implements Runnable { */ private String mouse(final boolean on) { if (on) { - return "\033[?1003;1005;1006h\033[?1049h"; + return "\033[?1002;1003;1005;1006h\033[?1049h"; } - return "\033[?1003;1006;1005l\033[?1049l"; + return "\033[?1002;1003;1006;1005l\033[?1049l"; } /** @@ -1584,17 +1515,18 @@ public final class ECMA48Terminal implements Runnable { for (int i = 0; i < rc; i++) { int ch = readBuffer[i]; processChar(events, (char)ch); - if (events.size() > 0) { - // Add to the queue for the backend thread to - // be able to obtain. - synchronized (eventQueue) { - eventQueue.addAll(events); - } - synchronized (listener) { - listener.notifyAll(); - } - events.clear(); + } + getIdleEvents(events); + if (events.size() > 0) { + // Add to the queue for the backend thread to + // be able to obtain. + synchronized (eventQueue) { + eventQueue.addAll(events); + } + synchronized (listener) { + listener.notifyAll(); } + events.clear(); } } } else {