diff --git a/src/dorkbox/util/input/Encoding.java b/src/dorkbox/util/input/Encoding.java index 2bed1d7..0fcc311 100644 --- a/src/dorkbox/util/input/Encoding.java +++ b/src/dorkbox/util/input/Encoding.java @@ -15,36 +15,36 @@ import java.nio.charset.Charset; public class Encoding { - /** - * Get the default encoding. Will first look at the LC_CTYPE environment variable, then the input.encoding system - * property, then the default charset according to the JVM. - * - * @return The default encoding to use when none is specified. - */ - public static String get() { - // LC_CTYPE is usually in the form en_US.UTF-8 - String envEncoding = extractEncodingFromCtype(System.getenv("LC_CTYPE")); - if (envEncoding != null) { - return envEncoding; + /** + * Get the default encoding. Will first look at the LC_CTYPE environment variable, then the input.encoding system property, then the + * default charset according to the JVM. + * + * @return The default encoding to use when none is specified. + */ + public static String get() { + // LC_CTYPE is usually in the form en_US.UTF-8 + String envEncoding = extractEncodingFromCtype(System.getenv("LC_CTYPE")); + if (envEncoding != null) { + return envEncoding; + } + return System.getProperty("input.encoding", Charset.defaultCharset().name()); } - return System.getProperty("input.encoding", Charset.defaultCharset().name()); - } - /** - * Parses the LC_CTYPE value to extract the encoding according to the POSIX standard, which says that the LC_CTYPE - * environment variable may be of the format [language[_territory][.codeset][@modifier]] - * - * @param ctype The ctype to parse, may be null - * @return The encoding, if one was present, otherwise null - */ - private static String extractEncodingFromCtype(String ctype) { - if (ctype != null && ctype.indexOf('.') > 0) { - String encodingAndModifier = ctype.substring(ctype.indexOf('.') + 1); - if (encodingAndModifier.indexOf('@') > 0) { - return encodingAndModifier.substring(0, encodingAndModifier.indexOf('@')); - } - return encodingAndModifier; + /** + * Parses the LC_CTYPE value to extract the encoding according to the POSIX standard, which says that the LC_CTYPE environment variable + * may be of the format [language[_territory][.codeset][@modifier]] + * + * @param ctype The ctype to parse, may be null + * @return The encoding, if one was present, otherwise null + */ + private static String extractEncodingFromCtype(String ctype) { + if (ctype != null && ctype.indexOf('.') > 0) { + String encodingAndModifier = ctype.substring(ctype.indexOf('.') + 1); + if (encodingAndModifier.indexOf('@') > 0) { + return encodingAndModifier.substring(0, encodingAndModifier.indexOf('@')); + } + return encodingAndModifier; + } + return null; } - return null; - } } diff --git a/src/dorkbox/util/input/InputConsole.java b/src/dorkbox/util/input/InputConsole.java index 44f478b..0e5c178 100644 --- a/src/dorkbox/util/input/InputConsole.java +++ b/src/dorkbox/util/input/InputConsole.java @@ -15,10 +15,6 @@ */ package dorkbox.util.input; -import org.fusesource.jansi.Ansi; -import org.fusesource.jansi.AnsiConsole; -import org.slf4j.Logger; - import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -29,6 +25,10 @@ import java.security.ProtectionDomain; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import org.fusesource.jansi.Ansi; +import org.fusesource.jansi.AnsiConsole; +import org.slf4j.Logger; + import dorkbox.util.OS; import dorkbox.util.bytes.ByteBuffer2; import dorkbox.util.bytes.ByteBuffer2Poolable; @@ -41,490 +41,487 @@ import dorkbox.util.objectPool.ObjectPoolHolder; public class InputConsole { - private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(InputConsole.class); - private static final InputConsole consoleProxyReader = new InputConsole(); - private static final char[] emptyLine = new char[0]; + private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(InputConsole.class); + private static final InputConsole consoleProxyReader = new InputConsole(); + private static final char[] emptyLine = new char[0]; - // this is run by our init... - static { - AnsiConsole.systemInstall(); + // this is run by our init... + static { + AnsiConsole.systemInstall(); - Thread consoleThread = new Thread(new Runnable() { - @Override - public void run() { - consoleProxyReader.run(); - } - }); - consoleThread.setDaemon(true); - consoleThread.setName("Console Input Reader"); + Thread consoleThread = new Thread(new Runnable() { + @Override + public void run() { + consoleProxyReader.run(); + } + }); + consoleThread.setDaemon(true); + consoleThread.setName("Console Input Reader"); - consoleThread.start(); + consoleThread.start(); - // has to be NOT DAEMON thread, since it must run before the app closes. + // has to be NOT DAEMON thread, since it must run before the app closes. - // don't forget we have to shut down the ansi console as well - // alternatively, shut everything down when the JVM closes. - Thread shutdownThread = new Thread() { - @Override - public void run() { - AnsiConsole.systemUninstall(); + // don't forget we have to shut down the ansi console as well + // alternatively, shut everything down when the JVM closes. + Thread shutdownThread = new Thread() { + @Override + public void run() { + AnsiConsole.systemUninstall(); - consoleProxyReader.shutdown0(); - } - }; - shutdownThread.setName("Console Input Shutdown"); - Runtime.getRuntime().addShutdownHook(shutdownThread); - } - - /** - * Permit our InputConsole to be initialized - */ - public static void init() { - if (logger.isDebugEnabled()) { - logger.debug("Created Terminal: {} ({}w x {}h)", consoleProxyReader.terminal.getClass().getSimpleName(), - consoleProxyReader.terminal.getWidth(), - consoleProxyReader.terminal.getHeight()); - } - } - - /** - * return null if no data - */ - public static String readLine() { - char[] line = consoleProxyReader.readLine0(); - return new String(line); - } - - private static InputStream wrappedInputStream = new InputStream() { - @Override - public int read() throws IOException { - return consoleProxyReader.read0(); + consoleProxyReader.shutdown0(); + } + }; + shutdownThread.setName("Console Input Shutdown"); + Runtime.getRuntime().addShutdownHook(shutdownThread); } - @Override - public void close() throws IOException { - consoleProxyReader.release0(); - } - }; - - - /** - * return -1 if no data - */ - public static int read() { - return consoleProxyReader.read0(); - } - - /** - * return null if no data - */ - public static char[] readLinePassword() { - return consoleProxyReader.readLinePassword0(); - } - - public static InputStream getInputStream() { - return wrappedInputStream; - } - - public static void echo(boolean enableEcho) { - consoleProxyReader.echo0(enableEcho); - } - - public static boolean echo() { - return consoleProxyReader.echo0(); - } - - private final Object inputLock = new Object(); - private final Object inputLockSingle = new Object(); - private final Object inputLockLine = new Object(); - - private final ObjectPool pool; - - private ThreadLocal> readBuff = new ThreadLocal>(); - private List> readBuffers = new CopyOnWriteArrayList>(); - private ThreadLocal threadBufferCounter = new ThreadLocal(); - - private ThreadLocal> readLineBuff = new ThreadLocal>(); - private List> - readLineBuffers = - new CopyOnWriteArrayList>(); - - private final Terminal terminal; - private final Boolean enableBackspace; - - private InputConsole() { - Logger logger = InputConsole.logger; - - String readers = System.getProperty(TerminalType.READERS); - int readers2 = 32; - if (readers != null) { - try { - readers2 = Integer.parseInt(readers); - } catch (Exception e) { - e.printStackTrace(); - } - } - this.pool = ObjectPoolFactory.create(new ByteBuffer2Poolable(), readers2); - - String type = System.getProperty(TerminalType.TYPE, TerminalType.AUTO).toLowerCase(); - if ("dumb".equals(System.getenv("TERM"))) { - type = TerminalType.NONE; - if (logger.isTraceEnabled()) { - logger.trace("System environment 'TERM'=dumb, creating type=" + type); - } - } else { - if (logger.isTraceEnabled()) { - logger.trace("Creating terminal, type=" + type); - } - } - - Terminal t; - try { - if (type.equals(TerminalType.UNIX)) { - t = new UnixTerminal(); - } else if (type.equals(TerminalType.WIN) || type.equals(TerminalType.WINDOWS)) { - t = new WindowsTerminal(); - } else if (type.equals(TerminalType.NONE) || type.equals(TerminalType.OFF) || type.equals(TerminalType.FALSE)) { - t = new UnsupportedTerminal(); - } else { - if (isIDEAutoDetect()) { - logger.debug( - "Terminal is in UNSUPPORTED (best guess). Unable to support single key input. Only line input available."); - t = new UnsupportedTerminal(); - } else { - if (OS.isWindows()) { - t = new WindowsTerminal(); - } else { - t = new UnixTerminal(); - } + /** + * Permit our InputConsole to be initialized + */ + public static void init() { + if (logger.isDebugEnabled()) { + logger.debug("Created Terminal: {} ({}w x {}h)", consoleProxyReader.terminal.getClass().getSimpleName(), + consoleProxyReader.terminal.getWidth(), + consoleProxyReader.terminal.getHeight()); } - } - } catch (Exception e) { - logger.error("Failed to construct terminal, falling back to unsupported"); - t = new UnsupportedTerminal(); } - try { - t.init(); - } catch (Throwable e) { - logger.error("Terminal initialization failed, falling back to unsupported"); - t = new UnsupportedTerminal(); - - try { - t.init(); - } catch (IOException e1) { - // UnsupportedTerminal can't do this - } + /** + * return null if no data + */ + public static String readLine() { + char[] line = consoleProxyReader.readLine0(); + return new String(line); } - t.setEchoEnabled(true); + private static InputStream wrappedInputStream = new InputStream() { + @Override + public int read() throws IOException { + return consoleProxyReader.read0(); + } - this.terminal = t; - this.enableBackspace = Boolean.parseBoolean(System.getProperty(TerminalType.ENABLE_BACKSPACE, "true")); - } + @Override + public void close() throws IOException { + consoleProxyReader.release0(); + } + }; - // called when the JVM is shutting down. - private void shutdown0() { - synchronized (this.inputLockSingle) { - this.inputLockSingle.notifyAll(); + + /** + * return -1 if no data + */ + public static int read() { + return consoleProxyReader.read0(); } - synchronized (this.inputLockLine) { - this.inputLockLine.notifyAll(); + /** + * return null if no data + */ + public static char[] readLinePassword() { + return consoleProxyReader.readLinePassword0(); } - try { - InputConsole inputConsole = InputConsole.this; - - inputConsole.terminal.restore(); - // this will 'hang' our shutdown, and honestly, who cares? We're shutting down anyways. - // inputConsole.reader.close(); // hangs on shutdown - } catch (IOException ignored) { - ignored.printStackTrace(); - } - } - - private void echo0(boolean enableEcho) { - this.terminal.setEchoEnabled(enableEcho); - } - - private boolean echo0() { - return this.terminal.isEchoEnabled(); - } - - /** - * return -1 if no data or bunged-up - */ - private int read0() { - Integer bufferCounter = this.threadBufferCounter.get(); - ObjectPoolHolder objectPoolHolder = this.readBuff.get(); - ByteBuffer2 buffer; - - if (objectPoolHolder == null) { - bufferCounter = 0; - this.threadBufferCounter.set(bufferCounter); - - ObjectPoolHolder holder = this.pool.take(); - buffer = holder.getValue(); - buffer.clear(); - this.readBuff.set(holder); - this.readBuffers.add(holder); - } else { - buffer = objectPoolHolder.getValue(); + public static InputStream getInputStream() { + return wrappedInputStream; } - if (bufferCounter == buffer.position()) { - synchronized (this.inputLockSingle) { - buffer.setPosition(0); - this.threadBufferCounter.set(0); + public static void echo(boolean enableEcho) { + consoleProxyReader.echo0(enableEcho); + } + + public static boolean echo() { + return consoleProxyReader.echo0(); + } + + private final Object inputLock = new Object(); + private final Object inputLockSingle = new Object(); + private final Object inputLockLine = new Object(); + + private final ObjectPool pool; + + private ThreadLocal> readBuff = new ThreadLocal>(); + private List> readBuffers = new CopyOnWriteArrayList>(); + private ThreadLocal threadBufferCounter = new ThreadLocal(); + + private ThreadLocal> readLineBuff = new ThreadLocal>(); + private List> readLineBuffers = new CopyOnWriteArrayList>(); + + private final Terminal terminal; + private final Boolean enableBackspace; + + private InputConsole() { + Logger logger = InputConsole.logger; + + String readers = System.getProperty(TerminalType.READERS); + int readers2 = 32; + if (readers != null) { + try { + readers2 = Integer.parseInt(readers); + } catch (Exception e) { + e.printStackTrace(); + } + } + this.pool = ObjectPoolFactory.create(new ByteBuffer2Poolable(), readers2); + + String type = System.getProperty(TerminalType.TYPE, TerminalType.AUTO).toLowerCase(); + if ("dumb".equals(System.getenv("TERM"))) { + type = TerminalType.NONE; + if (logger.isTraceEnabled()) { + logger.trace("System environment 'TERM'=dumb, creating type=" + type); + } + } else { + if (logger.isTraceEnabled()) { + logger.trace("Creating terminal, type=" + type); + } + } + + Terminal t; + try { + if (type.equals(TerminalType.UNIX)) { + t = new UnixTerminal(); + } else if (type.equals(TerminalType.WIN) || type.equals(TerminalType.WINDOWS)) { + t = new WindowsTerminal(); + } else if (type.equals(TerminalType.NONE) || type.equals(TerminalType.OFF) || type.equals(TerminalType.FALSE)) { + t = new UnsupportedTerminal(); + } else { + if (isIDEAutoDetect()) { + logger.debug("Terminal is in UNSUPPORTED (best guess). Unable to support single key input. Only line input available."); + t = new UnsupportedTerminal(); + } else { + if (OS.isWindows()) { + t = new WindowsTerminal(); + } else { + t = new UnixTerminal(); + } + } + } + } catch (Exception e) { + logger.error("Failed to construct terminal, falling back to unsupported"); + t = new UnsupportedTerminal(); + } try { - this.inputLockSingle.wait(); - } catch (InterruptedException e) { - return -1; - } - } - } + t.init(); + } catch (Throwable e) { + logger.error("Terminal initialization failed, falling back to unsupported"); + t = new UnsupportedTerminal(); - bufferCounter = this.threadBufferCounter.get(); - char c = buffer.readChar(bufferCounter); - bufferCounter += 2; - - this.threadBufferCounter.set(bufferCounter); - return c; - } - - /** - * return empty char[] if no data - */ - private char[] readLinePassword0() { - // don't bother in an IDE. it won't work. - boolean echoEnabled = this.terminal.isEchoEnabled(); - this.terminal.setEchoEnabled(false); - char[] readLine0 = readLine0(); - this.terminal.setEchoEnabled(echoEnabled); - - return readLine0; - } - - /** - * return empty char[] if no data - */ - private char[] readLine0() { - synchronized (this.inputLock) { - // empty here, because we don't want to register a readLine WHILE we are still processing - // the current line info. - - // the threadBufferForRead getting added is the part that is important - if (this.readLineBuff.get() == null) { - ObjectPoolHolder holder = this.pool.take(); - this.readLineBuff.set(holder); - this.readLineBuffers.add(holder); - } else { - this.readLineBuff.get().getValue().clear(); - } - } - - synchronized (this.inputLockLine) { - try { - this.inputLockLine.wait(); - } catch (InterruptedException e) { - return emptyLine; - } - } - - ObjectPoolHolder objectPoolHolder = this.readLineBuff.get(); - ByteBuffer2 buffer = objectPoolHolder.getValue(); - int len = buffer.position(); - if (len == 0) { - return emptyLine; - } - - buffer.rewind(); - char[] readChars = buffer.readChars(len / 2); // java always stores chars in 2 bytes - - // dump the chars in the buffer (safer for passwords, etc) - buffer.clearSecure(); - - this.readLineBuffers.remove(objectPoolHolder); - this.pool.release(objectPoolHolder); - this.readLineBuff.set(null); - - return readChars; - } - - /** - * releases any thread still waiting. - */ - private void release0() { - synchronized (this.inputLockSingle) { - this.inputLockSingle.notifyAll(); - } - - synchronized (this.inputLockLine) { - this.inputLockLine.notifyAll(); - } - } - - private void run() { - Logger logger2 = logger; - - final boolean ansiEnabled = Ansi.isEnabled(); - Ansi ansi = Ansi.ansi(); - PrintStream out = AnsiConsole.out; - - int typedChar; - char asChar; - - // don't type ; in a bash shell, it quits everything - // \n is replaced by \r in unix terminal? - while ((typedChar = this.terminal.read()) != -1) { - synchronized (this.inputLock) { - // don't let anyone add a new reader while we are still processing the current actions - asChar = (char) typedChar; - - if (logger2.isTraceEnabled()) { - logger2.trace("READ: {} ({})", asChar, typedChar); + try { + t.init(); + } catch (IOException e1) { + // UnsupportedTerminal can't do this + } } - // notify everyone waiting for a character. + t.setEchoEnabled(true); + + this.terminal = t; + this.enableBackspace = Boolean.parseBoolean(System.getProperty(TerminalType.ENABLE_BACKSPACE, "true")); + } + + // called when the JVM is shutting down. + private void shutdown0() { synchronized (this.inputLockSingle) { - // have to do readChar first (readLine has to deal with \b and \n - for (ObjectPoolHolder objectPoolHolder : this.readBuffers) { - ByteBuffer2 buffer = objectPoolHolder.getValue(); - buffer.writeChar(asChar); - } - - this.inputLockSingle.notifyAll(); + this.inputLockSingle.notifyAll(); } - // now to handle readLine stuff + synchronized (this.inputLockLine) { + this.inputLockLine.notifyAll(); + } - // if we type a backspace key, swallow it + previous in READLINE. READCHAR will have it passed. - if (this.enableBackspace && asChar == '\b') { - int position = 0; + try { + InputConsole inputConsole = InputConsole.this; - // clear ourself + one extra. - if (ansiEnabled) { - for (ObjectPoolHolder objectPoolHolder : this.readLineBuffers) { - ByteBuffer2 buffer = objectPoolHolder.getValue(); - // size of the buffer BEFORE our backspace was typed - int length = buffer.position(); - int amtToOverwrite = 2 * 2; // backspace is always 2 chars (^?) * 2 because it's bytes + inputConsole.terminal.restore(); + // this will 'hang' our shutdown, and honestly, who cares? We're shutting down anyways. + // inputConsole.reader.close(); // hangs on shutdown + } catch (IOException ignored) { + ignored.printStackTrace(); + } + } - if (length > 1) { - char charAt = buffer.readChar(length - 2); - amtToOverwrite += getPrintableCharacters(charAt); + private void echo0(boolean enableEcho) { + this.terminal.setEchoEnabled(enableEcho); + } - // delete last item in our buffer - length -= 2; - buffer.setPosition(length); + private boolean echo0() { + return this.terminal.isEchoEnabled(); + } - // now figure out where the cursor is really at. - // this is more memory friendly than buf.toString.length - for (int i = 0; i < length; i += 2) { - charAt = buffer.readChar(i); - position += getPrintableCharacters(charAt); + /** + * return -1 if no data or bunged-up + */ + private int read0() { + Integer bufferCounter = this.threadBufferCounter.get(); + ObjectPoolHolder objectPoolHolder = this.readBuff.get(); + ByteBuffer2 buffer; + + if (objectPoolHolder == null) { + bufferCounter = 0; + this.threadBufferCounter.set(bufferCounter); + + ObjectPoolHolder holder = this.pool.take(); + buffer = holder.getValue(); + buffer.clear(); + this.readBuff.set(holder); + this.readBuffers.add(holder); + } else { + buffer = objectPoolHolder.getValue(); + } + + if (bufferCounter == buffer.position()) { + synchronized (this.inputLockSingle) { + buffer.setPosition(0); + this.threadBufferCounter.set(0); + + try { + this.inputLockSingle.wait(); + } catch (InterruptedException e) { + return -1; + } + } + } + + bufferCounter = this.threadBufferCounter.get(); + char c = buffer.readChar(bufferCounter); + bufferCounter += 2; + + this.threadBufferCounter.set(bufferCounter); + return c; + } + + /** + * return empty char[] if no data + */ + private char[] readLinePassword0() { + // don't bother in an IDE. it won't work. + boolean echoEnabled = this.terminal.isEchoEnabled(); + this.terminal.setEchoEnabled(false); + char[] readLine0 = readLine0(); + this.terminal.setEchoEnabled(echoEnabled); + + return readLine0; + } + + /** + * return empty char[] if no data + */ + private char[] readLine0() { + synchronized (this.inputLock) { + // empty here, because we don't want to register a readLine WHILE we are still processing + // the current line info. + + // the threadBufferForRead getting added is the part that is important + if (this.readLineBuff.get() == null) { + ObjectPoolHolder holder = this.pool.take(); + this.readLineBuff.set(holder); + this.readLineBuffers.add(holder); + } else { + this.readLineBuff.get().getValue().clear(); + } + } + + synchronized (this.inputLockLine) { + try { + this.inputLockLine.wait(); + } catch (InterruptedException e) { + return emptyLine; + } + } + + ObjectPoolHolder objectPoolHolder = this.readLineBuff.get(); + ByteBuffer2 buffer = objectPoolHolder.getValue(); + int len = buffer.position(); + if (len == 0) { + return emptyLine; + } + + buffer.rewind(); + char[] readChars = buffer.readChars(len / 2); // java always stores chars in 2 bytes + + // dump the chars in the buffer (safer for passwords, etc) + buffer.clearSecure(); + + this.readLineBuffers.remove(objectPoolHolder); + this.pool.release(objectPoolHolder); + this.readLineBuff.set(null); + + return readChars; + } + + /** + * releases any thread still waiting. + */ + private void release0() { + synchronized (this.inputLockSingle) { + this.inputLockSingle.notifyAll(); + } + + synchronized (this.inputLockLine) { + this.inputLockLine.notifyAll(); + } + } + + private void run() { + Logger logger2 = logger; + + final boolean ansiEnabled = Ansi.isEnabled(); + Ansi ansi = Ansi.ansi(); + PrintStream out = AnsiConsole.out; + + int typedChar; + char asChar; + + // don't type ; in a bash shell, it quits everything + // \n is replaced by \r in unix terminal? + while ((typedChar = this.terminal.read()) != -1) { + synchronized (this.inputLock) { + // don't let anyone add a new reader while we are still processing the current actions + asChar = (char) typedChar; + + if (logger2.isTraceEnabled()) { + logger2.trace("READ: {} ({})", asChar, typedChar); } - position++; - } + // notify everyone waiting for a character. + synchronized (this.inputLockSingle) { + // have to do readChar first (readLine has to deal with \b and \n + for (ObjectPoolHolder objectPoolHolder : this.readBuffers) { + ByteBuffer2 buffer = objectPoolHolder.getValue(); + buffer.writeChar(asChar); + } - char[] overwrite = new char[amtToOverwrite]; - char c = ' '; - for (int i = 0; i < amtToOverwrite; i++) { - overwrite[i] = c; - } + this.inputLockSingle.notifyAll(); + } - // move back however many, over write, then go back again - out.print(ansi.cursorToColumn(position)); - out.print(overwrite); - out.print(ansi.cursorToColumn(position)); - out.flush(); + // now to handle readLine stuff + + // if we type a backspace key, swallow it + previous in READLINE. READCHAR will have it passed. + if (this.enableBackspace && asChar == '\b') { + int position = 0; + + // clear ourself + one extra. + if (ansiEnabled) { + for (ObjectPoolHolder objectPoolHolder : this.readLineBuffers) { + ByteBuffer2 buffer = objectPoolHolder.getValue(); + // size of the buffer BEFORE our backspace was typed + int length = buffer.position(); + int amtToOverwrite = 2 * 2; // backspace is always 2 chars (^?) * 2 because it's bytes + + if (length > 1) { + char charAt = buffer.readChar(length - 2); + amtToOverwrite += getPrintableCharacters(charAt); + + // delete last item in our buffer + length -= 2; + buffer.setPosition(length); + + // now figure out where the cursor is really at. + // this is more memory friendly than buf.toString.length + for (int i = 0; i < length; i += 2) { + charAt = buffer.readChar(i); + position += getPrintableCharacters(charAt); + } + + position++; + } + + char[] overwrite = new char[amtToOverwrite]; + char c = ' '; + for (int i = 0; i < amtToOverwrite; i++) { + overwrite[i] = c; + } + + // move back however many, over write, then go back again + out.print(ansi.cursorToColumn(position)); + out.print(overwrite); + out.print(ansi.cursorToColumn(position)); + out.flush(); + } + } + } else if (asChar == '\n') { + // ignoring \r, because \n is ALWAYS the last character in a new line sequence. (even for windows) + synchronized (this.inputLockLine) { + this.inputLockLine.notifyAll(); + } + } else { + // only append if we are not a new line. + // our windows console PREVENTS us from returning '\r' (it truncates '\r\n', and returns just '\n') + for (ObjectPoolHolder objectPoolHolder : this.readLineBuffers) { + ByteBuffer2 buffer = objectPoolHolder.getValue(); + buffer.writeChar(asChar); + } + } } - } - } else if (asChar == '\n') { - // ignoring \r, because \n is ALWAYS the last character in a new line sequence. (even for windows) - synchronized (this.inputLockLine) { - this.inputLockLine.notifyAll(); - } - } else { - // only append if we are not a new line. - // our windows console PREVENTS us from returning '\r' (it truncates '\r\n', and returns just '\n') - for (ObjectPoolHolder objectPoolHolder : this.readLineBuffers) { - ByteBuffer2 buffer = objectPoolHolder.getValue(); - buffer.writeChar(asChar); - } } - } - } - } - - /** - * try to guess if we are running inside an IDE - */ - private boolean isIDEAutoDetect() { - try { - // Get the location of this class - ProtectionDomain pDomain = getClass().getProtectionDomain(); - CodeSource cSource = pDomain.getCodeSource(); - URL loc = cSource.getLocation(); // file:/X:/workspace/xxxx/classes/ when it's in eclipse - - // if we are in eclipse, this won't be a jar -- it will be a class directory. - File locFile = new File(loc.getFile()); - return locFile.isDirectory(); - - } catch (Exception ignored) { } - // fall-back to unsupported - return true; - } + /** + * try to guess if we are running inside an IDE + */ + private boolean isIDEAutoDetect() { + try { + // Get the location of this class + ProtectionDomain pDomain = getClass().getProtectionDomain(); + CodeSource cSource = pDomain.getCodeSource(); + URL loc = cSource.getLocation(); // file:/X:/workspace/xxxx/classes/ when it's in eclipse + // if we are in eclipse, this won't be a jar -- it will be a class directory. + File locFile = new File(loc.getFile()); + return locFile.isDirectory(); - /** - * Return the number of characters that will be printed when the specified character is echoed to the screen - * - * Adapted from cat by Torbjorn Granlund, as repeated in stty by David MacKenzie. - */ - private static int getPrintableCharacters(final int ch) { -// StringBuilder sbuff = new StringBuilder(); - - if (ch >= 32) { - if (ch < 127) { -// sbuff.append((char) ch); - return 1; - } else if (ch == 127) { -// sbuff.append('^'); -// sbuff.append('?'); - return 2; - } else { -// sbuff.append('M'); -// sbuff.append('-'); - int count = 2; - - if (ch >= 128 + 32) { - if (ch < 128 + 127) { -// sbuff.append((char) (ch - 128)); - count++; - } else { -// sbuff.append('^'); -// sbuff.append('?'); - count += 2; - } - } else { -// sbuff.append('^'); -// sbuff.append((char) (ch - 128 + 64)); - count += 2; + } catch (Exception ignored) { } - return count; - } - } else { -// sbuff.append('^'); -// sbuff.append((char) (ch + 64)); - return 2; + + // fall-back to unsupported + return true; } -// return sbuff; - } + + /** + * Return the number of characters that will be printed when the specified character is echoed to the screen + * + * Adapted from cat by Torbjorn Granlund, as repeated in stty by David MacKenzie. + */ + private static int getPrintableCharacters(final int ch) { + // StringBuilder sbuff = new StringBuilder(); + + if (ch >= 32) { + if (ch < 127) { + // sbuff.append((char) ch); + return 1; + } else if (ch == 127) { + // sbuff.append('^'); + // sbuff.append('?'); + return 2; + } else { + // sbuff.append('M'); + // sbuff.append('-'); + int count = 2; + + if (ch >= 128 + 32) { + if (ch < 128 + 127) { + // sbuff.append((char) (ch - 128)); + count++; + } else { + // sbuff.append('^'); + // sbuff.append('?'); + count += 2; + } + } else { + // sbuff.append('^'); + // sbuff.append((char) (ch - 128 + 64)); + count += 2; + } + return count; + } + } else { + // sbuff.append('^'); + // sbuff.append((char) (ch + 64)); + return 2; + } + + // return sbuff; + } } diff --git a/src/dorkbox/util/input/Terminal.java b/src/dorkbox/util/input/Terminal.java index 92f057c..d800980 100644 --- a/src/dorkbox/util/input/Terminal.java +++ b/src/dorkbox/util/input/Terminal.java @@ -19,35 +19,34 @@ import java.io.IOException; public abstract class Terminal { - protected final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(getClass()); + protected final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(getClass()); - protected static final int DEFAULT_WIDTH = 80; - protected static final int DEFAULT_HEIGHT = 24; + protected static final int DEFAULT_WIDTH = 80; + protected static final int DEFAULT_HEIGHT = 24; - private volatile boolean echoEnabled; + private volatile boolean echoEnabled; - protected Terminal() { - } + protected Terminal() {} - public abstract void init() throws IOException; + public abstract void init() throws IOException; - public abstract void restore() throws IOException; + public abstract void restore() throws IOException; - public void setEchoEnabled(boolean enabled) { - this.echoEnabled = enabled; - } + public void setEchoEnabled(boolean enabled) { + this.echoEnabled = enabled; + } - public boolean isEchoEnabled() { - return this.echoEnabled; - } + public boolean isEchoEnabled() { + return this.echoEnabled; + } - public abstract int getWidth(); + public abstract int getWidth(); - public abstract int getHeight(); + public abstract int getHeight(); - /** - * @return a character from whatever underlying input method the terminal has available. - */ - public abstract int read(); + /** + * @return a character from whatever underlying input method the terminal has available. + */ + public abstract int read(); } diff --git a/src/dorkbox/util/input/TerminalType.java b/src/dorkbox/util/input/TerminalType.java index 6b5d7e0..b6171be 100644 --- a/src/dorkbox/util/input/TerminalType.java +++ b/src/dorkbox/util/input/TerminalType.java @@ -16,17 +16,16 @@ package dorkbox.util.input; public class TerminalType { + public static final String TYPE = "input.terminal"; + public static final String READERS = "input.terminal.readers"; + public static final String ENABLE_BACKSPACE = "input.enableBackspace"; - public static final String TYPE = "input.terminal"; - public static final String READERS = "input.terminal.readers"; - public static final String ENABLE_BACKSPACE = "input.enableBackspace"; + public static final String AUTO = "auto"; + public static final String UNIX = "unix"; + public static final String WIN = "win"; + public static final String WINDOWS = "windows"; - public static final String AUTO = "auto"; - public static final String UNIX = "unix"; - public static final String WIN = "win"; - public static final String WINDOWS = "windows"; - - public static final String NONE = "none"; - public static final String OFF = "off"; - public static final String FALSE = "false"; + public static final String NONE = "none"; + public static final String OFF = "off"; + public static final String FALSE = "false"; } diff --git a/src/dorkbox/util/input/posix/InputStreamReader.java b/src/dorkbox/util/input/posix/InputStreamReader.java index 0285b76..306d5d5 100644 --- a/src/dorkbox/util/input/posix/InputStreamReader.java +++ b/src/dorkbox/util/input/posix/InputStreamReader.java @@ -32,283 +32,275 @@ import java.nio.charset.UnmappableCharacterException; /** - * NOTE for JLine: the default InputStreamReader that comes from the JRE usually read more bytes than needed from the - * input stream, which is not usable in a character per character model used in the console. We thus use the harmony - * code which only reads the minimal number of bytes, with a modification to ensure we can read larger characters - * (UTF-16 has up to 4 bytes, and UTF-32, rare as it is, may have up to 8). + * NOTE for JLine: the default InputStreamReader that comes from the JRE usually read more bytes than needed from the input stream, which is + * not usable in a character per character model used in the console. We thus use the harmony code which only reads the minimal number of + * bytes, with a modification to ensure we can read larger characters (UTF-16 has up to 4 bytes, and UTF-32, rare as it is, may have up to + * 8). * * - * A class for turning a byte stream into a character stream. Data read from the source input stream is converted into - * characters by either a default or a provided character converter. The default encoding is taken from the - * "file.encoding" system property. {@code InputStreamReader} contains a buffer of bytes read from the source stream and - * converts these into characters as needed. The buffer size is 8K. + * A class for turning a byte stream into a character stream. Data read from the source input stream is converted into characters by either + * a default or a provided character converter. The default encoding is taken from the "file.encoding" system property. + * {@code InputStreamReader} contains a buffer of bytes read from the source stream and converts these into characters as needed. The buffer + * size is 8K. * * @see OutputStreamWriter */ @SuppressWarnings("ALL") class InputStreamReader extends Reader { - private InputStream in; + private InputStream in; - private static final int BUFFER_SIZE = 8192; + private static final int BUFFER_SIZE = 8192; - private boolean endOfInput = false; + private boolean endOfInput = false; - String encoding; + String encoding; - CharsetDecoder decoder; + CharsetDecoder decoder; - ByteBuffer bytes = ByteBuffer.allocate(BUFFER_SIZE); + ByteBuffer bytes = ByteBuffer.allocate(BUFFER_SIZE); - /** - * Constructs a new {@code InputStreamReader} on the {@link InputStream} {@code in}. This constructor sets the - * character converter to the encoding specified in the "file.encoding" property and falls back to ISO 8859_1 - * (ISO-Latin-1) if the property doesn't exist. - * - * @param in the input stream from which to read characters. - */ - public InputStreamReader(InputStream in) { - super(in); - this.in = in; - // FIXME: This should probably use Configuration.getFileEncoding() - this.encoding = System.getProperty("file.encoding", "ISO8859_1"); //$NON-NLS-1$//$NON-NLS-2$ - this.decoder = Charset.forName(this.encoding).newDecoder().onMalformedInput( - CodingErrorAction.REPLACE).onUnmappableCharacter( - CodingErrorAction.REPLACE); - this.bytes.limit(0); - } - - /** - * Constructs a new InputStreamReader on the InputStream {@code in}. The character converter that is used to decode - * bytes into characters is identified by name by {@code enc}. If the encoding cannot be found, an - * UnsupportedEncodingException error is thrown. - * - * @param in the InputStream from which to read characters. - * @param enc identifies the character converter to use. - * @throws NullPointerException if {@code enc} is {@code null}. - * @throws UnsupportedEncodingException if the encoding specified by {@code enc} cannot be found. - */ - public InputStreamReader(InputStream in, final String enc) -// throws UnsupportedEncodingException - { - super(in); - - if (enc == null) { - throw new NullPointerException(); + /** + * Constructs a new {@code InputStreamReader} on the {@link InputStream} {@code in}. This constructor sets the character converter to + * the encoding specified in the "file.encoding" property and falls back to ISO 8859_1 (ISO-Latin-1) if the property doesn't exist. + * + * @param in the input stream from which to read characters. + */ + public InputStreamReader(InputStream in) { + super(in); + this.in = in; + // FIXME: This should probably use Configuration.getFileEncoding() + this.encoding = System.getProperty("file.encoding", "ISO8859_1"); //$NON-NLS-1$//$NON-NLS-2$ + this.decoder = + Charset.forName(this.encoding).newDecoder().onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE); + this.bytes.limit(0); } - this.in = in; - try { - this.decoder = Charset.forName(enc).newDecoder().onMalformedInput( - CodingErrorAction.REPLACE).onUnmappableCharacter( - CodingErrorAction.REPLACE); - } catch (IllegalArgumentException e) { - e.printStackTrace(); -// throw (UnsupportedEncodingException) -// new UnsupportedEncodingException(enc).initCause(e); + /** + * Constructs a new InputStreamReader on the InputStream {@code in}. The character converter that is used to decode bytes into + * characters is identified by name by {@code enc}. If the encoding cannot be found, an UnsupportedEncodingException error is thrown. + * + * @param in the InputStream from which to read characters. + * @param enc identifies the character converter to use. + * @throws NullPointerException if {@code enc} is {@code null}. + * @throws UnsupportedEncodingException if the encoding specified by {@code enc} cannot be found. + */ + public InputStreamReader(InputStream in, final String enc) + // throws UnsupportedEncodingException + { + super(in); + + if (enc == null) { + throw new NullPointerException(); + } + this.in = in; + + try { + this.decoder = + Charset.forName(enc).newDecoder().onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + // throw (UnsupportedEncodingException) + // new UnsupportedEncodingException(enc).initCause(e); + } + this.bytes.limit(0); } - this.bytes.limit(0); - } - /** - * Constructs a new InputStreamReader on the InputStream {@code in} and CharsetDecoder {@code dec}. - * - * @param in the source InputStream from which to read characters. - * @param dec the CharsetDecoder used by the character conversion. - */ - public InputStreamReader(InputStream in, CharsetDecoder dec) { - super(in); - dec.averageCharsPerByte(); - this.in = in; - this.decoder = dec; - this.bytes.limit(0); - } - - /** - * Constructs a new InputStreamReader on the InputStream {@code in} and Charset {@code charset}. - * - * @param in the source InputStream from which to read characters. - * @param charset the Charset that defines the character converter - */ - public InputStreamReader(InputStream in, Charset charset) { - super(in); - this.in = in; - this.decoder = charset.newDecoder().onMalformedInput( - CodingErrorAction.REPLACE).onUnmappableCharacter( - CodingErrorAction.REPLACE); - this.bytes.limit(0); - } - - /** - * Closes this reader. This implementation closes the source InputStream and releases all local storage. - * - * @throws IOException if an error occurs attempting to close this reader. - */ - @Override - public void close() throws IOException { - synchronized (this.lock) { - this.decoder = null; - if (this.in != null) { - this.in.close(); - this.in = null; - } + /** + * Constructs a new InputStreamReader on the InputStream {@code in} and CharsetDecoder {@code dec}. + * + * @param in the source InputStream from which to read characters. + * @param dec the CharsetDecoder used by the character conversion. + */ + public InputStreamReader(InputStream in, CharsetDecoder dec) { + super(in); + dec.averageCharsPerByte(); + this.in = in; + this.decoder = dec; + this.bytes.limit(0); } - } - /** - * Returns the name of the encoding used to convert bytes into characters. The value {@code null} is returned if this - * reader has been closed. - * - * @return the name of the character converter or {@code null} if this reader is closed. - */ - public String getEncoding() { - if (!isOpen()) { - return null; + /** + * Constructs a new InputStreamReader on the InputStream {@code in} and Charset {@code charset}. + * + * @param in the source InputStream from which to read characters. + * @param charset the Charset that defines the character converter + */ + public InputStreamReader(InputStream in, Charset charset) { + super(in); + this.in = in; + this.decoder = charset.newDecoder().onMalformedInput(CodingErrorAction.REPLACE).onUnmappableCharacter(CodingErrorAction.REPLACE); + this.bytes.limit(0); } - return this.encoding; - } - /** - * Reads a single character from this reader and returns it as an integer with the two higher-order bytes set to 0. - * Returns -1 if the end of the reader has been reached. The byte value is either obtained from converting bytes in - * this reader's buffer or by first filling the buffer from the source InputStream and then reading from the buffer. - * - * @return the character read or -1 if the end of the reader has been reached. - * @throws IOException if this reader is closed or some other I/O error occurs. - */ - @Override - public int read() throws IOException { - synchronized (this.lock) { - if (!isOpen()) { - throw new IOException("InputStreamReader is closed."); - } - - char buf[] = new char[4]; - return read(buf, 0, 4) != -1 ? Character.codePointAt(buf, 0) : -1; - } - } - - /** - * Reads at most {@code length} characters from this reader and stores them at position {@code offset} in the - * character array {@code buf}. Returns the number of characters actually read or -1 if the end of the reader has been - * reached. The bytes are either obtained from converting bytes in this reader's buffer or by first filling the buffer - * from the source InputStream and then reading from the buffer. - * - * @param buf the array to store the characters read. - * @param offset the initial position in {@code buf} to store the characters read from this reader. - * @param length the maximum number of characters to read. - * @return the number of characters read or -1 if the end of the reader has been reached. - * @throws IndexOutOfBoundsException if {@code offset < 0} or {@code length < 0}, or if {@code offset + length} is - * greater than the length of {@code buf}. - * @throws IOException if this reader is closed or some other I/O error occurs. - */ - @Override - public int read(char[] buf, int offset, int length) throws IOException { - synchronized (this.lock) { - if (!isOpen()) { - throw new IOException("InputStreamReader is closed."); - } - if (offset < 0 || offset > buf.length - length || length < 0) { - throw new IndexOutOfBoundsException(); - } - if (length == 0) { - return 0; - } - - CharBuffer out = CharBuffer.wrap(buf, offset, length); - CoderResult result = CoderResult.UNDERFLOW; - - // bytes.remaining() indicates number of bytes in buffer - // when 1-st time entered, it'll be equal to zero - boolean needInput = !this.bytes.hasRemaining(); - - while (out.hasRemaining()) { - // fill the buffer if needed - if (needInput) { - try { - if (this.in.available() == 0 - && out.position() > offset) { - // we could return the result without blocking read - break; + /** + * Closes this reader. This implementation closes the source InputStream and releases all local storage. + * + * @throws IOException if an error occurs attempting to close this reader. + */ + @Override + public void close() throws IOException { + synchronized (this.lock) { + this.decoder = null; + if (this.in != null) { + this.in.close(); + this.in = null; } - } catch (IOException e) { - // available didn't work so just try the read - } - - int to_read = this.bytes.capacity() - this.bytes.limit(); - int off = this.bytes.arrayOffset() + this.bytes.limit(); - int was_red = this.in.read(this.bytes.array(), off, to_read); - - if (was_red == -1) { - this.endOfInput = true; - break; - } else if (was_red == 0) { - break; - } - this.bytes.limit(this.bytes.limit() + was_red); - needInput = false; } + } - // decode bytes - result = this.decoder.decode(this.bytes, out, false); - - if (result.isUnderflow()) { - // compact the buffer if no space left - if (this.bytes.limit() == this.bytes.capacity()) { - this.bytes.compact(); - this.bytes.limit(this.bytes.position()); - this.bytes.position(0); - } - needInput = true; - } else { - break; + /** + * Returns the name of the encoding used to convert bytes into characters. The value {@code null} is returned if this reader has been + * closed. + * + * @return the name of the character converter or {@code null} if this reader is closed. + */ + public String getEncoding() { + if (!isOpen()) { + return null; } - } - - if (result == CoderResult.UNDERFLOW && this.endOfInput) { - result = this.decoder.decode(this.bytes, out, true); - this.decoder.flush(out); - this.decoder.reset(); - } - if (result.isMalformed()) { - throw new MalformedInputException(result.length()); - } else if (result.isUnmappable()) { - throw new UnmappableCharacterException(result.length()); - } - - return out.position() - offset == 0 ? -1 : out.position() - offset; + return this.encoding; } - } - /* - * Answer a boolean indicating whether or not this InputStreamReader is - * open. - */ - private boolean isOpen() { - return this.in != null; - } + /** + * Reads a single character from this reader and returns it as an integer with the two higher-order bytes set to 0. Returns -1 if the + * end of the reader has been reached. The byte value is either obtained from converting bytes in this reader's buffer or by first + * filling the buffer from the source InputStream and then reading from the buffer. + * + * @return the character read or -1 if the end of the reader has been reached. + * @throws IOException if this reader is closed or some other I/O error occurs. + */ + @Override + public int read() throws IOException { + synchronized (this.lock) { + if (!isOpen()) { + throw new IOException("InputStreamReader is closed."); + } - /** - * Indicates whether this reader is ready to be read without blocking. If the result is {@code true}, the next {@code - * read()} will not block. If the result is {@code false} then this reader may or may not block when {@code read()} is - * called. This implementation returns {@code true} if there are bytes available in the buffer or the source stream - * has bytes available. - * - * @return {@code true} if the receiver will not block when {@code read()} is called, {@code false} if unknown or - * blocking will occur. - * @throws IOException if this reader is closed or some other I/O error occurs. - */ - @Override - public boolean ready() throws IOException { - synchronized (this.lock) { - if (this.in == null) { - throw new IOException("InputStreamReader is closed."); - } - try { - return this.bytes.hasRemaining() || this.in.available() > 0; - } catch (IOException e) { - return false; - } + char buf[] = new char[4]; + return read(buf, 0, 4) != -1 ? Character.codePointAt(buf, 0) : -1; + } + } + + /** + * Reads at most {@code length} characters from this reader and stores them at position {@code offset} in the character array + * {@code buf}. Returns the number of characters actually read or -1 if the end of the reader has been reached. The bytes are either + * obtained from converting bytes in this reader's buffer or by first filling the buffer from the source InputStream and then reading + * from the buffer. + * + * @param buf the array to store the characters read. + * @param offset the initial position in {@code buf} to store the characters read from this reader. + * @param length the maximum number of characters to read. + * @return the number of characters read or -1 if the end of the reader has been reached. + * @throws IndexOutOfBoundsException if {@code offset < 0} or {@code length < 0}, or if {@code offset + length} is greater than the + * length of {@code buf}. + * @throws IOException if this reader is closed or some other I/O error occurs. + */ + @Override + public int read(char[] buf, int offset, int length) throws IOException { + synchronized (this.lock) { + if (!isOpen()) { + throw new IOException("InputStreamReader is closed."); + } + if (offset < 0 || offset > buf.length - length || length < 0) { + throw new IndexOutOfBoundsException(); + } + if (length == 0) { + return 0; + } + + CharBuffer out = CharBuffer.wrap(buf, offset, length); + CoderResult result = CoderResult.UNDERFLOW; + + // bytes.remaining() indicates number of bytes in buffer + // when 1-st time entered, it'll be equal to zero + boolean needInput = !this.bytes.hasRemaining(); + + while (out.hasRemaining()) { + // fill the buffer if needed + if (needInput) { + try { + if (this.in.available() == 0 && out.position() > offset) { + // we could return the result without blocking read + break; + } + } catch (IOException e) { + // available didn't work so just try the read + } + + int to_read = this.bytes.capacity() - this.bytes.limit(); + int off = this.bytes.arrayOffset() + this.bytes.limit(); + int was_red = this.in.read(this.bytes.array(), off, to_read); + + if (was_red == -1) { + this.endOfInput = true; + break; + } else if (was_red == 0) { + break; + } + this.bytes.limit(this.bytes.limit() + was_red); + needInput = false; + } + + // decode bytes + result = this.decoder.decode(this.bytes, out, false); + + if (result.isUnderflow()) { + // compact the buffer if no space left + if (this.bytes.limit() == this.bytes.capacity()) { + this.bytes.compact(); + this.bytes.limit(this.bytes.position()); + this.bytes.position(0); + } + needInput = true; + } else { + break; + } + } + + if (result == CoderResult.UNDERFLOW && this.endOfInput) { + result = this.decoder.decode(this.bytes, out, true); + this.decoder.flush(out); + this.decoder.reset(); + } + if (result.isMalformed()) { + throw new MalformedInputException(result.length()); + } else if (result.isUnmappable()) { + throw new UnmappableCharacterException(result.length()); + } + + return out.position() - offset == 0 ? -1 : out.position() - offset; + } + } + + /* + * Answer a boolean indicating whether or not this InputStreamReader is open. + */ + private boolean isOpen() { + return this.in != null; + } + + /** + * Indicates whether this reader is ready to be read without blocking. If the result is {@code true}, the next {@code read()} will not + * block. If the result is {@code false} then this reader may or may not block when {@code read()} is called. This implementation + * returns {@code true} if there are bytes available in the buffer or the source stream has bytes available. + * + * @return {@code true} if the receiver will not block when {@code read()} is called, {@code false} if unknown or blocking will occur. + * @throws IOException if this reader is closed or some other I/O error occurs. + */ + @Override + public boolean ready() throws IOException { + synchronized (this.lock) { + if (this.in == null) { + throw new IOException("InputStreamReader is closed."); + } + try { + return this.bytes.hasRemaining() || this.in.available() > 0; + } catch (IOException e) { + return false; + } + } } - } } diff --git a/src/dorkbox/util/input/posix/PosixTerminalControl.java b/src/dorkbox/util/input/posix/PosixTerminalControl.java index cfb2148..4574d12 100644 --- a/src/dorkbox/util/input/posix/PosixTerminalControl.java +++ b/src/dorkbox/util/input/posix/PosixTerminalControl.java @@ -15,71 +15,71 @@ */ package dorkbox.util.input.posix; -import com.sun.jna.Library; - import java.nio.ByteBuffer; +import com.sun.jna.Library; + @SuppressWarnings("ALL") interface PosixTerminalControl extends Library { - public static final int TCSANOW = 0; - public static final int TBUFLEN = 124; + public static final int TCSANOW = 0; + public static final int TBUFLEN = 124; - // Definitions at: http://linux.die.net/man/3/termios - // also: http://code.metager.de/source/xref/DragonFly-BSD/sys/sys/termios.h - public static final int IGNBRK = 0x00000001; /* ignore BREAK condition */ - public static final int BRKINT = 0x00000002; /* map BREAK to SIGINTR */ + // Definitions at: http://linux.die.net/man/3/termios + // also: http://code.metager.de/source/xref/DragonFly-BSD/sys/sys/termios.h + public static final int IGNBRK = 0x00000001; /* ignore BREAK condition */ + public static final int BRKINT = 0x00000002; /* map BREAK to SIGINTR */ - public static final int ISIG = 0000001; - public static final int ICANON = 0000002; - public static final int ECHO = 0000010; - public static final int IXON = 0002000; + public static final int ISIG = 0000001; + public static final int ICANON = 0000002; + public static final int ECHO = 0000010; + public static final int IXON = 0002000; - public static final int VINTR = 0; - public static final int VQUIT = 1; - public static final int VERASE = 2; - public static final int VKILL = 3; - public static final int VEOF = 4; - public static final int VTIME = 5; - public static final int VMIN = 6; - public static final int VSWTC = 7; - public static final int VSTART = 8; - public static final int VSTOP = 9; - public static final int VSUSP = 10; - public static final int VEOL = 11; - public static final int VREPRINT = 12; - public static final int VDISCARD = 13; - public static final int VWERASE = 14; - public static final int VLNEXT = 15; - public static final int VEOL2 = 16; + public static final int VINTR = 0; + public static final int VQUIT = 1; + public static final int VERASE = 2; + public static final int VKILL = 3; + public static final int VEOF = 4; + public static final int VTIME = 5; + public static final int VMIN = 6; + public static final int VSWTC = 7; + public static final int VSTART = 8; + public static final int VSTOP = 9; + public static final int VSUSP = 10; + public static final int VEOL = 11; + public static final int VREPRINT = 12; + public static final int VDISCARD = 13; + public static final int VWERASE = 14; + public static final int VLNEXT = 15; + public static final int VEOL2 = 16; - // MAGIC! - public static final int TIOCGWINSZ = System.getProperty("os.name").equalsIgnoreCase("linux") ? 0x5413 : 1074295912; + // MAGIC! + public static final int TIOCGWINSZ = System.getProperty("os.name").equalsIgnoreCase("linux") ? 0x5413 : 1074295912; - public int open(String path, int flags); + public int open(String path, int flags); - public int close(int fd); + public int close(int fd); - /** - * Original signature : int ioctl(int, int, char*)
- */ - public int ioctl(int d, int request, ByteBuffer data); + /** + * Original signature : int ioctl(int, int, char*)
+ */ + public int ioctl(int d, int request, ByteBuffer data); - /** - * Put the state of FD into *TERMIOS_P.
- * - * Original signature : int tcgetattr(int, char*)
- */ - public int tcgetattr(int fd, TermiosStruct termios_p); + /** + * Put the state of FD into *TERMIOS_P.
+ * + * Original signature : int tcgetattr(int, char*)
+ */ + public int tcgetattr(int fd, TermiosStruct termios_p); - /** - * Set the state of FD to *TERMIOS_P.
- * - * Values for OPTIONAL_ACTIONS (TCSA*) are in .
- * - * Original signature : int tcsetattr(int, int, char*)
- */ - public int tcsetattr(int fd, int optional_actions, TermiosStruct termios_p); + /** + * Set the state of FD to *TERMIOS_P.
+ * + * Values for OPTIONAL_ACTIONS (TCSA*) are in .
+ * + * Original signature : int tcsetattr(int, int, char*)
+ */ + public int tcsetattr(int fd, int optional_actions, TermiosStruct termios_p); } diff --git a/src/dorkbox/util/input/posix/TermiosStruct.java b/src/dorkbox/util/input/posix/TermiosStruct.java index 05ae22f..1564894 100644 --- a/src/dorkbox/util/input/posix/TermiosStruct.java +++ b/src/dorkbox/util/input/posix/TermiosStruct.java @@ -15,55 +15,53 @@ */ package dorkbox.util.input.posix; -import com.sun.jna.Structure; - import java.util.Arrays; import java.util.List; +import com.sun.jna.Structure; + @SuppressWarnings("ALL") class TermiosStruct extends Structure { + /** + * input mode flags + */ + public int c_iflag; + /** + * output mode flags + */ + public int c_oflag; + /** + * control mode flags + */ + public int c_cflag; + /** + * local mode flags + */ + public int c_lflag; + /** + * line discipline + */ + public byte c_line; - /** - * input mode flags - */ - public int c_iflag; - /** - * output mode flags - */ - public int c_oflag; - /** - * control mode flags - */ - public int c_cflag; - /** - * local mode flags - */ - public int c_lflag; - /** - * line discipline - */ - public byte c_line; + /** + * control characters + */ + public byte[] c_cc = new byte[32]; - /** - * control characters - */ - public byte[] c_cc = new byte[32]; + /** + * input speed + */ + public int c_ispeed; + /** + * output speed + */ + public int c_ospeed; - /** - * input speed - */ - public int c_ispeed; - /** - * output speed - */ - public int c_ospeed; + public TermiosStruct() {} - public TermiosStruct() { - } - - @Override - protected List getFieldOrder() { - return Arrays.asList( + @Override + protected List getFieldOrder() { + return Arrays.asList( "c_iflag", "c_oflag", "c_cflag", @@ -71,7 +69,6 @@ class TermiosStruct extends Structure { "c_line", "c_cc", "c_ispeed", - "c_ospeed" - ); - } + "c_ospeed"); + } } diff --git a/src/dorkbox/util/input/posix/UnixTerminal.java b/src/dorkbox/util/input/posix/UnixTerminal.java index e6bad18..f640453 100644 --- a/src/dorkbox/util/input/posix/UnixTerminal.java +++ b/src/dorkbox/util/input/posix/UnixTerminal.java @@ -15,46 +15,45 @@ */ package dorkbox.util.input.posix; -import com.sun.jna.Native; - import java.io.IOException; import java.io.Reader; import java.nio.ByteBuffer; +import com.sun.jna.Native; + import dorkbox.util.input.Encoding; import dorkbox.util.input.Terminal; /** - * Terminal that is used for unix platforms. Terminal initialization is handled via JNA and - * ioctl/tcgetattr/tcsetattr/cfmakeraw. + * Terminal that is used for unix platforms. Terminal initialization is handled via JNA and ioctl/tcgetattr/tcsetattr/cfmakeraw. * * This implementation should work for an reasonable POSIX system. */ public class UnixTerminal extends Terminal { - private volatile TermiosStruct termInfoDefault = new TermiosStruct(); - private volatile TermiosStruct termInfo = new TermiosStruct(); + private volatile TermiosStruct termInfoDefault = new TermiosStruct(); + private volatile TermiosStruct termInfo = new TermiosStruct(); - private final Reader reader; + private final Reader reader; - private final PosixTerminalControl term; - private ByteBuffer windowSizeBuffer = ByteBuffer.allocate(8); + private final PosixTerminalControl term; + private ByteBuffer windowSizeBuffer = ByteBuffer.allocate(8); - public UnixTerminal() throws Exception { - String encoding = Encoding.get(); - this.reader = new InputStreamReader(System.in, encoding); + public UnixTerminal() throws Exception { + String encoding = Encoding.get(); + this.reader = new InputStreamReader(System.in, encoding); - this.term = (PosixTerminalControl) Native.loadLibrary("c", PosixTerminalControl.class); + this.term = (PosixTerminalControl) Native.loadLibrary("c", PosixTerminalControl.class); - // save off the defaults - if (this.term.tcgetattr(0, this.termInfoDefault) != 0) { - throw new IOException("Failed to get terminal info"); + // save off the defaults + if (this.term.tcgetattr(0, this.termInfoDefault) != 0) { + throw new IOException("Failed to get terminal info"); + } } - } - @Override - public void init() throws IOException { + @Override + public void init() throws IOException { // COMPARABLE TO (from upstream) //settings.set("-icanon min 1 -ixon"); @@ -77,118 +76,118 @@ public class UnixTerminal extends Terminal { // t->c_cc[VMIN] = 1; // t->c_cc[VTIME] = 0; - if (this.term.tcgetattr(0, this.termInfo) != 0) { - throw new IOException("Failed to get terminal info"); + if (this.term.tcgetattr(0, this.termInfo) != 0) { + throw new IOException("Failed to get terminal info"); + } + + this.termInfo.c_iflag &= ~PosixTerminalControl.IXON; // DISABLE - flow control mediated by ^S and ^Q + // struct.c_iflag |= PosixTerminalControl.IUTF8; // DISABLE - flow control mediated by ^S and ^Q + + this.termInfo.c_lflag &= ~PosixTerminalControl.ICANON; // DISABLE - canonical mode (pass chars straight through to terminal) + // struct.c_lflag &= ~PosixTerminalControl.ISIG; // DISABLE - When any of the characters INTR, QUIT, SUSP, or DSUSP are received, + // generate the corresponding signal. + + // If MIN > 0 and TIME = 0, MIN sets the number of characters to receive before the read is satisfied. As TIME is zero, the timer is + // not used. + this.termInfo.c_cc[PosixTerminalControl.VMIN] = 1; // Minimum number of characters for noncanonical read (MIN). + this.termInfo.c_cc[PosixTerminalControl.VTIME] = 0; // Timeout in deciseconds for noncanonical read (TIME). + + this.termInfo.c_cc[PosixTerminalControl.VSUSP] = 0; // suspend disabled + this.termInfo.c_cc[PosixTerminalControl.VEOF] = 0; // eof disabled + this.termInfo.c_cc[PosixTerminalControl.VEOL] = 0; // eol disabled + + if (this.term.tcsetattr(0, PosixTerminalControl.TCSANOW, this.termInfo) != 0) { + throw new IOException("Can not set terminal flags"); + } } - this.termInfo.c_iflag &= ~PosixTerminalControl.IXON; // DISABLE - flow control mediated by ^S and ^Q -// struct.c_iflag |= PosixTerminalControl.IUTF8; // DISABLE - flow control mediated by ^S and ^Q - - this.termInfo.c_lflag &= - ~PosixTerminalControl.ICANON; // DISABLE - canonical mode (pass chars straight through to terminal) -// struct.c_lflag &= ~PosixTerminalControl.ISIG; // DISABLE - When any of the characters INTR, QUIT, SUSP, or DSUSP are received, generate the corresponding signal. - - // If MIN > 0 and TIME = 0, MIN sets the number of characters to receive before the read is satisfied. As TIME is zero, the timer is not used. - this.termInfo.c_cc[PosixTerminalControl.VMIN] = 1; // Minimum number of characters for noncanonical read (MIN). - this.termInfo.c_cc[PosixTerminalControl.VTIME] = 0; // Timeout in deciseconds for noncanonical read (TIME). - - this.termInfo.c_cc[PosixTerminalControl.VSUSP] = 0; // suspend disabled - this.termInfo.c_cc[PosixTerminalControl.VEOF] = 0; // eof disabled - this.termInfo.c_cc[PosixTerminalControl.VEOL] = 0; // eol disabled - - if (this.term.tcsetattr(0, PosixTerminalControl.TCSANOW, this.termInfo) != 0) { - throw new IOException("Can not set terminal flags"); - } - } - - /** - * Restore the original terminal configuration, which can be used when shutting down the console reader. The - * ConsoleReader cannot be used after calling this method. - */ - @Override - public final void restore() throws IOException { - if (this.term.tcsetattr(0, PosixTerminalControl.TCSANOW, this.termInfoDefault) != 0) { - throw new IOException("Can not reset terminal to defaults"); - } - } - - /** - * Returns number of columns in the terminal. - */ - @Override - public final int getWidth() { - if (this.term.ioctl(0, PosixTerminalControl.TIOCGWINSZ, this.windowSizeBuffer) != 0) { - return DEFAULT_WIDTH; + /** + * Restore the original terminal configuration, which can be used when shutting down the console reader. The ConsoleReader cannot be + * used after calling this method. + */ + @Override + public final void restore() throws IOException { + if (this.term.tcsetattr(0, PosixTerminalControl.TCSANOW, this.termInfoDefault) != 0) { + throw new IOException("Can not reset terminal to defaults"); + } } - return (short) (0x000000FF & this.windowSizeBuffer.get(2) + (0x000000FF & this.windowSizeBuffer.get(3)) * 256); - } + /** + * Returns number of columns in the terminal. + */ + @Override + public final int getWidth() { + if (this.term.ioctl(0, PosixTerminalControl.TIOCGWINSZ, this.windowSizeBuffer) != 0) { + return DEFAULT_WIDTH; + } - /** - * Returns number of rows in the terminal. - */ - @Override - public final int getHeight() { - if (this.term.ioctl(0, PosixTerminalControl.TIOCGWINSZ, this.windowSizeBuffer) != 0) { - return DEFAULT_HEIGHT; + return (short) (0x000000FF & this.windowSizeBuffer.get(2) + (0x000000FF & this.windowSizeBuffer.get(3)) * 256); } - return - (short) (0x000000FF & this.windowSizeBuffer.get(0) + (0x000000FF & this.windowSizeBuffer.get(1)) * 256); - } + /** + * Returns number of rows in the terminal. + */ + @Override + public final int getHeight() { + if (this.term.ioctl(0, PosixTerminalControl.TIOCGWINSZ, this.windowSizeBuffer) != 0) { + return DEFAULT_HEIGHT; + } - @Override - public final synchronized void setEchoEnabled(final boolean enabled) { - // have to reget them, since flags change everything - if (this.term.tcgetattr(0, this.termInfo) != 0) { - this.logger.error("Failed to get terminal info"); + return (short) (0x000000FF & this.windowSizeBuffer.get(0) + (0x000000FF & this.windowSizeBuffer.get(1)) * 256); } - if (enabled) { - this.termInfo.c_lflag |= PosixTerminalControl.ECHO; // ENABLE Echo input characters. - } else { - this.termInfo.c_lflag &= ~PosixTerminalControl.ECHO; // DISABLE Echo input characters. + @Override + public final synchronized void setEchoEnabled(final boolean enabled) { + // have to reget them, since flags change everything + if (this.term.tcgetattr(0, this.termInfo) != 0) { + this.logger.error("Failed to get terminal info"); + } + + if (enabled) { + this.termInfo.c_lflag |= PosixTerminalControl.ECHO; // ENABLE Echo input characters. + } else { + this.termInfo.c_lflag &= ~PosixTerminalControl.ECHO; // DISABLE Echo input characters. + } + + if (this.term.tcsetattr(0, PosixTerminalControl.TCSANOW, this.termInfo) != 0) { + this.logger.error("Can not set terminal flags"); + } + + super.setEchoEnabled(enabled); } - if (this.term.tcsetattr(0, PosixTerminalControl.TCSANOW, this.termInfo) != 0) { - this.logger.error("Can not set terminal flags"); + // public final void disableInterruptCharacter() { + // // have to re-get them, since flags change everything + // if (this.term.tcgetattr(0, this.termInfo) !=0) { + // this.logger.error("Failed to get terminal info"); + // } + // + // this.termInfo.c_cc[PosixTerminalControl.VINTR] = 0; // interrupt disabled + // + // if (this.term.tcsetattr(0, PosixTerminalControl.TCSANOW, this.termInfo) != 0) { + // this.logger.error("Can not set terminal flags"); + // } + // } + // + // public final void enableInterruptCharacter() { + // // have to re-get them, since flags change everything + // if (this.term.tcgetattr(0, this.termInfo) !=0) { + // this.logger.error("Failed to get terminal info"); + // } + // + // this.termInfo.c_cc[PosixTerminalControl.VINTR] = 3; // interrupt is ctrl-c + // + // if (this.term.tcsetattr(0, PosixTerminalControl.TCSANOW, this.termInfo) != 0) { + // this.logger.error("Can not set terminal flags"); + // } + // } + + @Override + public final int read() { + try { + return this.reader.read(); + } catch (IOException ignored) { + return -1; + } } - - super.setEchoEnabled(enabled); - } - -// public final void disableInterruptCharacter() { -// // have to re-get them, since flags change everything -// if (this.term.tcgetattr(0, this.termInfo) !=0) { -// this.logger.error("Failed to get terminal info"); -// } -// -// this.termInfo.c_cc[PosixTerminalControl.VINTR] = 0; // interrupt disabled -// -// if (this.term.tcsetattr(0, PosixTerminalControl.TCSANOW, this.termInfo) != 0) { -// this.logger.error("Can not set terminal flags"); -// } -// } -// -// public final void enableInterruptCharacter() { -// // have to re-get them, since flags change everything -// if (this.term.tcgetattr(0, this.termInfo) !=0) { -// this.logger.error("Failed to get terminal info"); -// } -// -// this.termInfo.c_cc[PosixTerminalControl.VINTR] = 3; // interrupt is ctrl-c -// -// if (this.term.tcsetattr(0, PosixTerminalControl.TCSANOW, this.termInfo) != 0) { -// this.logger.error("Can not set terminal flags"); -// } -// } - - @Override - public final int read() { - try { - return this.reader.read(); - } catch (IOException ignored) { - return -1; - } - } } diff --git a/src/dorkbox/util/input/unsupported/UnsupportedTerminal.java b/src/dorkbox/util/input/unsupported/UnsupportedTerminal.java index 3d1747b..39968f4 100644 --- a/src/dorkbox/util/input/unsupported/UnsupportedTerminal.java +++ b/src/dorkbox/util/input/unsupported/UnsupportedTerminal.java @@ -23,66 +23,64 @@ import dorkbox.util.input.Terminal; public class UnsupportedTerminal extends Terminal { - private final ByteBuffer2 buffer = new ByteBuffer2(8, -1); + private final ByteBuffer2 buffer = new ByteBuffer2(8, -1); - private int readerCount = -1; - private final InputStream in; + private int readerCount = -1; + private final InputStream in; - public UnsupportedTerminal() { - this.in = System.in; - } + public UnsupportedTerminal() { + this.in = System.in; + } - @Override - public final void init() throws IOException { - } + @Override + public final void init() throws IOException {} - @Override - public final void restore() { - } + @Override + public final void restore() {} - @Override - public final int getWidth() { - return 0; - } + @Override + public final int getWidth() { + return 0; + } - @Override - public final int getHeight() { - return 0; - } + @Override + public final int getHeight() { + return 0; + } - @Override - public final int read() { - // if we are reading data (because we are in IDE mode), we want to return ALL the chars of the line! + @Override + public final int read() { + // if we are reading data (because we are in IDE mode), we want to return ALL the chars of the line! - // so, 'readerCount' is REALLY the index at which we return letters (until the whole string is returned) - if (this.readerCount == -1) { - // we have to wait for more data. - try { - InputStream sysIn = this.in; - int read; - char asChar; - this.buffer.clearSecure(); + // so, 'readerCount' is REALLY the index at which we return letters (until the whole string is returned) + if (this.readerCount == -1) { + // we have to wait for more data. + try { + InputStream sysIn = this.in; + int read; + char asChar; + this.buffer.clearSecure(); - while ((read = sysIn.read()) != -1) { - asChar = (char) read; - if (asChar == '\n') { - this.readerCount = this.buffer.position(); - this.buffer.rewind(); - break; - } else { - this.buffer.writeChar(asChar); - } + while ((read = sysIn.read()) != -1) { + asChar = (char) read; + if (asChar == '\n') { + this.readerCount = this.buffer.position(); + this.buffer.rewind(); + break; + } else { + this.buffer.writeChar(asChar); + } + } + } catch (IOException ignored) { + } } - } catch (IOException ignored) { - } - } - // EACH thread will have it's own count! - if (this.readerCount == this.buffer.position()) { - this.readerCount = -1; - return '\n'; - } else { - return this.buffer.readChar(); + // EACH thread will have it's own count! + if (this.readerCount == this.buffer.position()) { + this.readerCount = -1; + return '\n'; + } else { + return this.buffer.readChar(); + } } - } } diff --git a/src/dorkbox/util/input/windows/ConsoleMode.java b/src/dorkbox/util/input/windows/ConsoleMode.java index 66e0bcb..0833dea 100644 --- a/src/dorkbox/util/input/windows/ConsoleMode.java +++ b/src/dorkbox/util/input/windows/ConsoleMode.java @@ -12,47 +12,48 @@ package dorkbox.util.input.windows; /** - * Console mode

Constants copied wincon.h. + * Console mode + *

+ * Constants copied wincon.h. */ public enum ConsoleMode { - /** - * The ReadFile or ReadConsole function returns only when a carriage return character is read. If this mode is - * disable, the functions return when one or more characters are available. - */ - ENABLE_LINE_INPUT(2), + /** + * The ReadFile or ReadConsole function returns only when a carriage return character is read. If this mode is disable, the functions + * return when one or more characters are available. + */ + ENABLE_LINE_INPUT(2), - /** - * Characters read by the ReadFile or ReadConsole function are written to the active screen buffer as they are read. - * This mode can be used only if the ENABLE_LINE_INPUT mode is also enabled. - */ - ENABLE_ECHO_INPUT(4), + /** + * Characters read by the ReadFile or ReadConsole function are written to the active screen buffer as they are read. This mode can be + * used only if the ENABLE_LINE_INPUT mode is also enabled. + */ + ENABLE_ECHO_INPUT(4), - /** - * CTRL+C is processed by the system and is not placed in the input buffer. If the input buffer is being read by - * ReadFile or ReadConsole, other control keys are processed by the system and are not returned in the ReadFile or - * ReadConsole buffer. If the ENABLE_LINE_INPUT mode is also enabled, backspace, carriage return, and linefeed - * characters are handled by the system. - */ - ENABLE_PROCESSED_INPUT(1), + /** + * CTRL+C is processed by the system and is not placed in the input buffer. If the input buffer is being read by ReadFile or + * ReadConsole, other control keys are processed by the system and are not returned in the ReadFile or ReadConsole buffer. If the + * ENABLE_LINE_INPUT mode is also enabled, backspace, carriage return, and linefeed characters are handled by the system. + */ + ENABLE_PROCESSED_INPUT(1), - /** - * User interactions that change the size of the console screen buffer are reported in the console's input buffee. - * Information about these events can be read from the input buffer by applications using theReadConsoleInput - * function, but not by those using ReadFile orReadConsole. - */ - ENABLE_WINDOW_INPUT(8), + /** + * User interactions that change the size of the console screen buffer are reported in the console's input buffee. Information about + * these events can be read from the input buffer by applications using theReadConsoleInput function, but not by those using ReadFile + * orReadConsole. + */ + ENABLE_WINDOW_INPUT(8), - /** - * If the mouse pointer is within the borders of the console window and the window has the keyboard focus, mouse - * events generated by mouse movement and button presses are placed in the input buffer. These events are discarded by - * ReadFile or ReadConsole, even when this mode is enabled. - */ - ENABLE_MOUSE_INPUT(16),; + /** + * If the mouse pointer is within the borders of the console window and the window has the keyboard focus, mouse events generated by + * mouse movement and button presses are placed in the input buffer. These events are discarded by ReadFile or ReadConsole, even when + * this mode is enabled. + */ + ENABLE_MOUSE_INPUT(16), ; - public final int code; + public final int code; - ConsoleMode(final int code) { - this.code = code; - } + ConsoleMode(final int code) { + this.code = code; + } } diff --git a/src/dorkbox/util/input/windows/WindowsTerminal.java b/src/dorkbox/util/input/windows/WindowsTerminal.java index 6e1a3ed..a94dedd 100644 --- a/src/dorkbox/util/input/windows/WindowsTerminal.java +++ b/src/dorkbox/util/input/windows/WindowsTerminal.java @@ -11,114 +11,115 @@ */ package dorkbox.util.input.windows; +import java.io.IOException; +import java.io.PrintStream; + import org.fusesource.jansi.internal.Kernel32.INPUT_RECORD; import org.fusesource.jansi.internal.Kernel32.KEY_EVENT_RECORD; import org.fusesource.jansi.internal.WindowsSupport; -import java.io.IOException; -import java.io.PrintStream; - import dorkbox.util.input.Terminal; /** - * Terminal implementation for Microsoft Windows. Terminal initialization in {@link #init} is accomplished by calling - * the Win32 APIs SetConsoleMode - * and GetConsoleMode - * to disable character echoing.

+ * Terminal implementation for Microsoft Windows. Terminal initialization in {@link #init} is accomplished by calling the Win32 APIs SetConsoleMode and GetConsoleMode to disable + * character echoing. + *

* * @since 2.0 (customized) */ public class WindowsTerminal extends Terminal { - private volatile int originalMode; - private final PrintStream out; + private volatile int originalMode; + private final PrintStream out; - public WindowsTerminal() { - this.out = System.out; - } + public WindowsTerminal() { + this.out = System.out; + } - @Override - public final void init() throws IOException { - this.originalMode = WindowsSupport.getConsoleMode(); + @Override + public final void init() throws IOException { + this.originalMode = WindowsSupport.getConsoleMode(); - // Must set these four modes at the same time to make it work fine. - WindowsSupport.setConsoleMode(this.originalMode | + // Must set these four modes at the same time to make it work fine. + WindowsSupport.setConsoleMode(this.originalMode | ConsoleMode.ENABLE_LINE_INPUT.code | ConsoleMode.ENABLE_ECHO_INPUT.code | ConsoleMode.ENABLE_PROCESSED_INPUT.code | ConsoleMode.ENABLE_WINDOW_INPUT.code); - } - - /** - * Restore the original terminal configuration, which can be used when shutting down the console reader. The - * ConsoleReader cannot be used after calling this method. - */ - @Override - public final void restore() throws IOException { - // restore the old console mode - WindowsSupport.setConsoleMode(this.originalMode); - } - - @Override - public final int getWidth() { - int w = WindowsSupport.getWindowsTerminalWidth(); - return w < 1 ? DEFAULT_WIDTH : w; - } - - @Override - public final int getHeight() { - int h = WindowsSupport.getWindowsTerminalHeight(); - return h < 1 ? DEFAULT_HEIGHT : h; - } - - @Override - public final int read() { - int input = readInput(); - - if (isEchoEnabled()) { - char asChar = (char) input; - if (asChar == '\n') { - this.out.println(); - } else { - this.out.print(asChar); - } - // have to flush, otherwise we'll never see the chars on screen - this.out.flush(); } - return input; - } + /** + * Restore the original terminal configuration, which can be used when shutting down the console reader. The ConsoleReader cannot be + * used after calling this method. + */ + @Override + public final void restore() throws IOException { + // restore the old console mode + WindowsSupport.setConsoleMode(this.originalMode); + } - private int readInput() { - // this HOOKS the input event, and prevents it from going to the console "proper" - try { - INPUT_RECORD[] events; - while (true) { - // we ALWAYS read until we have an event we care about! - events = WindowsSupport.readConsoleInput(1); + @Override + public final int getWidth() { + int w = WindowsSupport.getWindowsTerminalWidth(); + return w < 1 ? DEFAULT_WIDTH : w; + } - if (events != null) { - for (int i = 0; i < events.length; i++) { - KEY_EVENT_RECORD keyEvent = events[i].keyEvent; - //Log.trace(keyEvent.keyDown? "KEY_DOWN" : "KEY_UP", "key code:", keyEvent.keyCode, "char:", (long)keyEvent.uchar); - if (keyEvent.keyDown) { - if (keyEvent.uchar > 0) { - char uchar = keyEvent.uchar; - if (uchar == '\r') { - // we purposefully swallow input after \r, and substitute it with \n - return '\n'; - } + @Override + public final int getHeight() { + int h = WindowsSupport.getWindowsTerminalHeight(); + return h < 1 ? DEFAULT_HEIGHT : h; + } - return uchar; - } + @Override + public final int read() { + int input = readInput(); + + if (isEchoEnabled()) { + char asChar = (char) input; + if (asChar == '\n') { + this.out.println(); + } else { + this.out.print(asChar); } - } + // have to flush, otherwise we'll never see the chars on screen + this.out.flush(); } - } - } catch (IOException e) { - this.logger.error("Windows console input error: ", e); + + return input; } - return -1; - } + private int readInput() { + // this HOOKS the input event, and prevents it from going to the console "proper" + try { + INPUT_RECORD[] events; + while (true) { + // we ALWAYS read until we have an event we care about! + events = WindowsSupport.readConsoleInput(1); + + if (events != null) { + for (int i = 0; i < events.length; i++) { + KEY_EVENT_RECORD keyEvent = events[i].keyEvent; + // Log.trace(keyEvent.keyDown? "KEY_DOWN" : "KEY_UP", "key code:", keyEvent.keyCode, "char:", (long)keyEvent.uchar); + if (keyEvent.keyDown) { + if (keyEvent.uchar > 0) { + char uchar = keyEvent.uchar; + if (uchar == '\r') { + // we purposefully swallow input after \r, and substitute it with \n + return '\n'; + } + + return uchar; + } + } + } + } + } + } catch (IOException e) { + this.logger.error("Windows console input error: ", e); + } + + return -1; + } }