Modified google code format

This commit is contained in:
nathan 2014-11-24 17:45:14 +01:00
parent ed2a472a6f
commit a33e62b098
11 changed files with 1115 additions and 1132 deletions

View File

@ -15,36 +15,36 @@ import java.nio.charset.Charset;
public class Encoding { public class Encoding {
/** /**
* Get the default encoding. Will first look at the LC_CTYPE environment variable, then the input.encoding system * Get the default encoding. Will first look at the LC_CTYPE environment variable, then the input.encoding system property, then the
* property, then the default charset according to the JVM. * default charset according to the JVM.
* *
* @return The default encoding to use when none is specified. * @return The default encoding to use when none is specified.
*/ */
public static String get() { public static String get() {
// LC_CTYPE is usually in the form en_US.UTF-8 // LC_CTYPE is usually in the form en_US.UTF-8
String envEncoding = extractEncodingFromCtype(System.getenv("LC_CTYPE")); String envEncoding = extractEncodingFromCtype(System.getenv("LC_CTYPE"));
if (envEncoding != null) { if (envEncoding != null) {
return envEncoding; 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 * Parses the LC_CTYPE value to extract the encoding according to the POSIX standard, which says that the LC_CTYPE environment variable
* environment variable may be of the format <code>[language[_territory][.codeset][@modifier]]</code> * may be of the format <code>[language[_territory][.codeset][@modifier]]</code>
* *
* @param ctype The ctype to parse, may be null * @param ctype The ctype to parse, may be null
* @return The encoding, if one was present, otherwise null * @return The encoding, if one was present, otherwise null
*/ */
private static String extractEncodingFromCtype(String ctype) { private static String extractEncodingFromCtype(String ctype) {
if (ctype != null && ctype.indexOf('.') > 0) { if (ctype != null && ctype.indexOf('.') > 0) {
String encodingAndModifier = ctype.substring(ctype.indexOf('.') + 1); String encodingAndModifier = ctype.substring(ctype.indexOf('.') + 1);
if (encodingAndModifier.indexOf('@') > 0) { if (encodingAndModifier.indexOf('@') > 0) {
return encodingAndModifier.substring(0, encodingAndModifier.indexOf('@')); return encodingAndModifier.substring(0, encodingAndModifier.indexOf('@'));
} }
return encodingAndModifier; return encodingAndModifier;
}
return null;
} }
return null;
}
} }

View File

@ -15,10 +15,6 @@
*/ */
package dorkbox.util.input; 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.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -29,6 +25,10 @@ import java.security.ProtectionDomain;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; 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.OS;
import dorkbox.util.bytes.ByteBuffer2; import dorkbox.util.bytes.ByteBuffer2;
import dorkbox.util.bytes.ByteBuffer2Poolable; import dorkbox.util.bytes.ByteBuffer2Poolable;
@ -41,490 +41,487 @@ import dorkbox.util.objectPool.ObjectPoolHolder;
public class InputConsole { public class InputConsole {
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(InputConsole.class); private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(InputConsole.class);
private static final InputConsole consoleProxyReader = new InputConsole(); private static final InputConsole consoleProxyReader = new InputConsole();
private static final char[] emptyLine = new char[0]; private static final char[] emptyLine = new char[0];
// this is run by our init... // this is run by our init...
static { static {
AnsiConsole.systemInstall(); AnsiConsole.systemInstall();
Thread consoleThread = new Thread(new Runnable() { Thread consoleThread = new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
consoleProxyReader.run(); consoleProxyReader.run();
} }
}); });
consoleThread.setDaemon(true); consoleThread.setDaemon(true);
consoleThread.setName("Console Input Reader"); 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 // don't forget we have to shut down the ansi console as well
// alternatively, shut everything down when the JVM closes. // alternatively, shut everything down when the JVM closes.
Thread shutdownThread = new Thread() { Thread shutdownThread = new Thread() {
@Override @Override
public void run() { public void run() {
AnsiConsole.systemUninstall(); AnsiConsole.systemUninstall();
consoleProxyReader.shutdown0(); consoleProxyReader.shutdown0();
} }
}; };
shutdownThread.setName("Console Input Shutdown"); shutdownThread.setName("Console Input Shutdown");
Runtime.getRuntime().addShutdownHook(shutdownThread); 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();
} }
@Override /**
public void close() throws IOException { * Permit our InputConsole to be initialized
consoleProxyReader.release0(); */
} 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 -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<ByteBuffer2> pool;
private ThreadLocal<ObjectPoolHolder<ByteBuffer2>> readBuff = new ThreadLocal<ObjectPoolHolder<ByteBuffer2>>();
private List<ObjectPoolHolder<ByteBuffer2>> readBuffers = new CopyOnWriteArrayList<ObjectPoolHolder<ByteBuffer2>>();
private ThreadLocal<Integer> threadBufferCounter = new ThreadLocal<Integer>();
private ThreadLocal<ObjectPoolHolder<ByteBuffer2>> readLineBuff = new ThreadLocal<ObjectPoolHolder<ByteBuffer2>>();
private List<ObjectPoolHolder<ByteBuffer2>>
readLineBuffers =
new CopyOnWriteArrayList<ObjectPoolHolder<ByteBuffer2>>();
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 { /**
t.init(); * return null if no data
} catch (Throwable e) { */
logger.error("Terminal initialization failed, falling back to unsupported"); public static String readLine() {
t = new UnsupportedTerminal(); char[] line = consoleProxyReader.readLine0();
return new String(line);
try {
t.init();
} catch (IOException e1) {
// UnsupportedTerminal can't do this
}
} }
t.setEchoEnabled(true); private static InputStream wrappedInputStream = new InputStream() {
@Override
public int read() throws IOException {
return consoleProxyReader.read0();
}
this.terminal = t; @Override
this.enableBackspace = Boolean.parseBoolean(System.getProperty(TerminalType.ENABLE_BACKSPACE, "true")); public void close() throws IOException {
} consoleProxyReader.release0();
}
};
// called when the JVM is shutting down.
private void shutdown0() { /**
synchronized (this.inputLockSingle) { * return -1 if no data
this.inputLockSingle.notifyAll(); */
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 { public static InputStream getInputStream() {
InputConsole inputConsole = InputConsole.this; return wrappedInputStream;
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<ByteBuffer2> objectPoolHolder = this.readBuff.get();
ByteBuffer2 buffer;
if (objectPoolHolder == null) {
bufferCounter = 0;
this.threadBufferCounter.set(bufferCounter);
ObjectPoolHolder<ByteBuffer2> 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()) { public static void echo(boolean enableEcho) {
synchronized (this.inputLockSingle) { consoleProxyReader.echo0(enableEcho);
buffer.setPosition(0); }
this.threadBufferCounter.set(0);
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<ByteBuffer2> pool;
private ThreadLocal<ObjectPoolHolder<ByteBuffer2>> readBuff = new ThreadLocal<ObjectPoolHolder<ByteBuffer2>>();
private List<ObjectPoolHolder<ByteBuffer2>> readBuffers = new CopyOnWriteArrayList<ObjectPoolHolder<ByteBuffer2>>();
private ThreadLocal<Integer> threadBufferCounter = new ThreadLocal<Integer>();
private ThreadLocal<ObjectPoolHolder<ByteBuffer2>> readLineBuff = new ThreadLocal<ObjectPoolHolder<ByteBuffer2>>();
private List<ObjectPoolHolder<ByteBuffer2>> readLineBuffers = new CopyOnWriteArrayList<ObjectPoolHolder<ByteBuffer2>>();
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 { try {
this.inputLockSingle.wait(); t.init();
} catch (InterruptedException e) { } catch (Throwable e) {
return -1; logger.error("Terminal initialization failed, falling back to unsupported");
} t = new UnsupportedTerminal();
}
}
bufferCounter = this.threadBufferCounter.get(); try {
char c = buffer.readChar(bufferCounter); t.init();
bufferCounter += 2; } catch (IOException e1) {
// UnsupportedTerminal can't do this
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<ByteBuffer2> 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<ByteBuffer2> 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);
} }
// 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) { synchronized (this.inputLockSingle) {
// have to do readChar first (readLine has to deal with \b and \n this.inputLockSingle.notifyAll();
for (ObjectPoolHolder<ByteBuffer2> objectPoolHolder : this.readBuffers) {
ByteBuffer2 buffer = objectPoolHolder.getValue();
buffer.writeChar(asChar);
}
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. try {
if (this.enableBackspace && asChar == '\b') { InputConsole inputConsole = InputConsole.this;
int position = 0;
// clear ourself + one extra. inputConsole.terminal.restore();
if (ansiEnabled) { // this will 'hang' our shutdown, and honestly, who cares? We're shutting down anyways.
for (ObjectPoolHolder<ByteBuffer2> objectPoolHolder : this.readLineBuffers) { // inputConsole.reader.close(); // hangs on shutdown
ByteBuffer2 buffer = objectPoolHolder.getValue(); } catch (IOException ignored) {
// size of the buffer BEFORE our backspace was typed ignored.printStackTrace();
int length = buffer.position(); }
int amtToOverwrite = 2 * 2; // backspace is always 2 chars (^?) * 2 because it's bytes }
if (length > 1) { private void echo0(boolean enableEcho) {
char charAt = buffer.readChar(length - 2); this.terminal.setEchoEnabled(enableEcho);
amtToOverwrite += getPrintableCharacters(charAt); }
// delete last item in our buffer private boolean echo0() {
length -= 2; return this.terminal.isEchoEnabled();
buffer.setPosition(length); }
// now figure out where the cursor is really at. /**
// this is more memory friendly than buf.toString.length * return -1 if no data or bunged-up
for (int i = 0; i < length; i += 2) { */
charAt = buffer.readChar(i); private int read0() {
position += getPrintableCharacters(charAt); Integer bufferCounter = this.threadBufferCounter.get();
ObjectPoolHolder<ByteBuffer2> objectPoolHolder = this.readBuff.get();
ByteBuffer2 buffer;
if (objectPoolHolder == null) {
bufferCounter = 0;
this.threadBufferCounter.set(bufferCounter);
ObjectPoolHolder<ByteBuffer2> 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<ByteBuffer2> 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<ByteBuffer2> 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<ByteBuffer2> objectPoolHolder : this.readBuffers) {
ByteBuffer2 buffer = objectPoolHolder.getValue();
buffer.writeChar(asChar);
}
char[] overwrite = new char[amtToOverwrite]; this.inputLockSingle.notifyAll();
char c = ' '; }
for (int i = 0; i < amtToOverwrite; i++) {
overwrite[i] = c;
}
// move back however many, over write, then go back again // now to handle readLine stuff
out.print(ansi.cursorToColumn(position));
out.print(overwrite); // if we type a backspace key, swallow it + previous in READLINE. READCHAR will have it passed.
out.print(ansi.cursorToColumn(position)); if (this.enableBackspace && asChar == '\b') {
out.flush(); int position = 0;
// clear ourself + one extra.
if (ansiEnabled) {
for (ObjectPoolHolder<ByteBuffer2> 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<ByteBuffer2> 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<ByteBuffer2> 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();
/** } catch (Exception ignored) {
* 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;
} // fall-back to unsupported
} else { return true;
// sbuff.append('^');
// sbuff.append((char) (ch + 64));
return 2;
} }
// 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;
}
} }

View File

@ -19,35 +19,34 @@ import java.io.IOException;
public abstract class Terminal { 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_WIDTH = 80;
protected static final int DEFAULT_HEIGHT = 24; 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) { public void setEchoEnabled(boolean enabled) {
this.echoEnabled = enabled; this.echoEnabled = enabled;
} }
public boolean isEchoEnabled() { public boolean isEchoEnabled() {
return this.echoEnabled; 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. * @return a character from whatever underlying input method the terminal has available.
*/ */
public abstract int read(); public abstract int read();
} }

View File

@ -16,17 +16,16 @@
package dorkbox.util.input; package dorkbox.util.input;
public class TerminalType { 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 AUTO = "auto";
public static final String READERS = "input.terminal.readers"; public static final String UNIX = "unix";
public static final String ENABLE_BACKSPACE = "input.enableBackspace"; public static final String WIN = "win";
public static final String WINDOWS = "windows";
public static final String AUTO = "auto"; public static final String NONE = "none";
public static final String UNIX = "unix"; public static final String OFF = "off";
public static final String WIN = "win"; public static final String FALSE = "false";
public static final String WINDOWS = "windows";
public static final String NONE = "none";
public static final String OFF = "off";
public static final String FALSE = "false";
} }

View File

@ -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 * NOTE for JLine: the default InputStreamReader that comes from the JRE usually read more bytes than needed from the input stream, which is
* input stream, which is not usable in a character per character model used in the console. We thus use the harmony * 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
* code which only reads the minimal number of bytes, with a modification to ensure we can read larger characters * 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
* (UTF-16 has up to 4 bytes, and UTF-32, rare as it is, may have up to 8). * 8).
* *
* *
* A class for turning a byte stream into a character stream. Data read from the source input stream is converted into * A class for turning a byte stream into a character stream. Data read from the source input stream is converted into characters by either
* characters by either a default or a provided character converter. The default encoding is taken from the * a default or a provided character converter. The default encoding is taken from the "file.encoding" system property.
* "file.encoding" system property. {@code InputStreamReader} contains a buffer of bytes read from the source stream and * {@code InputStreamReader} contains a buffer of bytes read from the source stream and converts these into characters as needed. The buffer
* converts these into characters as needed. The buffer size is 8K. * size is 8K.
* *
* @see OutputStreamWriter * @see OutputStreamWriter
*/ */
@SuppressWarnings("ALL") @SuppressWarnings("ALL")
class InputStreamReader extends Reader { 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 * Constructs a new {@code InputStreamReader} on the {@link InputStream} {@code in}. This constructor sets the character converter to
* character converter to the encoding specified in the "file.encoding" property and falls back to ISO 8859_1 * the encoding specified in the "file.encoding" property and falls back to ISO 8859_1 (ISO-Latin-1) if the property doesn't exist.
* (ISO-Latin-1) if the property doesn't exist. *
* * @param in the input stream from which to read characters.
* @param in the input stream from which to read characters. */
*/ public InputStreamReader(InputStream in) {
public InputStreamReader(InputStream in) { super(in);
super(in); this.in = in;
this.in = in; // FIXME: This should probably use Configuration.getFileEncoding()
// FIXME: This should probably use Configuration.getFileEncoding() this.encoding = System.getProperty("file.encoding", "ISO8859_1"); //$NON-NLS-1$//$NON-NLS-2$
this.encoding = System.getProperty("file.encoding", "ISO8859_1"); //$NON-NLS-1$//$NON-NLS-2$ this.decoder =
this.decoder = Charset.forName(this.encoding).newDecoder().onMalformedInput( Charset.forName(this.encoding).newDecoder().onMalformedInput(CodingErrorAction.REPLACE)
CodingErrorAction.REPLACE).onUnmappableCharacter( .onUnmappableCharacter(CodingErrorAction.REPLACE);
CodingErrorAction.REPLACE); this.bytes.limit(0);
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();
} }
this.in = in;
try { /**
this.decoder = Charset.forName(enc).newDecoder().onMalformedInput( * Constructs a new InputStreamReader on the InputStream {@code in}. The character converter that is used to decode bytes into
CodingErrorAction.REPLACE).onUnmappableCharacter( * characters is identified by name by {@code enc}. If the encoding cannot be found, an UnsupportedEncodingException error is thrown.
CodingErrorAction.REPLACE); *
} catch (IllegalArgumentException e) { * @param in the InputStream from which to read characters.
e.printStackTrace(); * @param enc identifies the character converter to use.
// throw (UnsupportedEncodingException) * @throws NullPointerException if {@code enc} is {@code null}.
// new UnsupportedEncodingException(enc).initCause(e); * @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}. * Constructs a new InputStreamReader on the InputStream {@code in} and CharsetDecoder {@code dec}.
* *
* @param in the source InputStream from which to read characters. * @param in the source InputStream from which to read characters.
* @param dec the CharsetDecoder used by the character conversion. * @param dec the CharsetDecoder used by the character conversion.
*/ */
public InputStreamReader(InputStream in, CharsetDecoder dec) { public InputStreamReader(InputStream in, CharsetDecoder dec) {
super(in); super(in);
dec.averageCharsPerByte(); dec.averageCharsPerByte();
this.in = in; this.in = in;
this.decoder = dec; this.decoder = dec;
this.bytes.limit(0); 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;
}
} }
}
/** /**
* Returns the name of the encoding used to convert bytes into characters. The value {@code null} is returned if this * Constructs a new InputStreamReader on the InputStream {@code in} and Charset {@code charset}.
* reader has been closed. *
* * @param in the source InputStream from which to read characters.
* @return the name of the character converter or {@code null} if this reader is closed. * @param charset the Charset that defines the character converter
*/ */
public String getEncoding() { public InputStreamReader(InputStream in, Charset charset) {
if (!isOpen()) { super(in);
return null; 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. * Closes this reader. This implementation closes the source InputStream and releases all local storage.
* 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. * @throws IOException if an error occurs attempting to close this reader.
* */
* @return the character read or -1 if the end of the reader has been reached. @Override
* @throws IOException if this reader is closed or some other I/O error occurs. public void close() throws IOException {
*/ synchronized (this.lock) {
@Override this.decoder = null;
public int read() throws IOException { if (this.in != null) {
synchronized (this.lock) { this.in.close();
if (!isOpen()) { this.in = null;
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;
} }
} 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); * Returns the name of the encoding used to convert bytes into characters. The value {@code null} is returned if this reader has been
* closed.
if (result.isUnderflow()) { *
// compact the buffer if no space left * @return the name of the character converter or {@code null} if this reader is closed.
if (this.bytes.limit() == this.bytes.capacity()) { */
this.bytes.compact(); public String getEncoding() {
this.bytes.limit(this.bytes.position()); if (!isOpen()) {
this.bytes.position(0); return null;
}
needInput = true;
} else {
break;
} }
} return this.encoding;
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 * 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
* open. * 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.
private boolean isOpen() { *
return this.in != null; * @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];
* Indicates whether this reader is ready to be read without blocking. If the result is {@code true}, the next {@code return read(buf, 0, 4) != -1 ? Character.codePointAt(buf, 0) : -1;
* 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 * Reads at most {@code length} characters from this reader and stores them at position {@code offset} in the character array
* blocking will occur. * {@code buf}. Returns the number of characters actually read or -1 if the end of the reader has been reached. The bytes are either
* @throws IOException if this reader is closed or some other I/O error occurs. * 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.
@Override *
public boolean ready() throws IOException { * @param buf the array to store the characters read.
synchronized (this.lock) { * @param offset the initial position in {@code buf} to store the characters read from this reader.
if (this.in == null) { * @param length the maximum number of characters to read.
throw new IOException("InputStreamReader is closed."); * @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
try { * length of {@code buf}.
return this.bytes.hasRemaining() || this.in.available() > 0; * @throws IOException if this reader is closed or some other I/O error occurs.
} catch (IOException e) { */
return false; @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;
}
}
} }
}
} }

View File

@ -15,71 +15,71 @@
*/ */
package dorkbox.util.input.posix; package dorkbox.util.input.posix;
import com.sun.jna.Library;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import com.sun.jna.Library;
@SuppressWarnings("ALL") @SuppressWarnings("ALL")
interface PosixTerminalControl extends Library { interface PosixTerminalControl extends Library {
public static final int TCSANOW = 0; public static final int TCSANOW = 0;
public static final int TBUFLEN = 124; public static final int TBUFLEN = 124;
// Definitions at: http://linux.die.net/man/3/termios // Definitions at: http://linux.die.net/man/3/termios
// also: http://code.metager.de/source/xref/DragonFly-BSD/sys/sys/termios.h // 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 IGNBRK = 0x00000001; /* ignore BREAK condition */
public static final int BRKINT = 0x00000002; /* map BREAK to SIGINTR */ public static final int BRKINT = 0x00000002; /* map BREAK to SIGINTR */
public static final int ISIG = 0000001; public static final int ISIG = 0000001;
public static final int ICANON = 0000002; public static final int ICANON = 0000002;
public static final int ECHO = 0000010; public static final int ECHO = 0000010;
public static final int IXON = 0002000; public static final int IXON = 0002000;
public static final int VINTR = 0; public static final int VINTR = 0;
public static final int VQUIT = 1; public static final int VQUIT = 1;
public static final int VERASE = 2; public static final int VERASE = 2;
public static final int VKILL = 3; public static final int VKILL = 3;
public static final int VEOF = 4; public static final int VEOF = 4;
public static final int VTIME = 5; public static final int VTIME = 5;
public static final int VMIN = 6; public static final int VMIN = 6;
public static final int VSWTC = 7; public static final int VSWTC = 7;
public static final int VSTART = 8; public static final int VSTART = 8;
public static final int VSTOP = 9; public static final int VSTOP = 9;
public static final int VSUSP = 10; public static final int VSUSP = 10;
public static final int VEOL = 11; public static final int VEOL = 11;
public static final int VREPRINT = 12; public static final int VREPRINT = 12;
public static final int VDISCARD = 13; public static final int VDISCARD = 13;
public static final int VWERASE = 14; public static final int VWERASE = 14;
public static final int VLNEXT = 15; public static final int VLNEXT = 15;
public static final int VEOL2 = 16; public static final int VEOL2 = 16;
// MAGIC! // MAGIC!
public static final int TIOCGWINSZ = System.getProperty("os.name").equalsIgnoreCase("linux") ? 0x5413 : 1074295912; 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 : <code>int ioctl(int, int, char*)</code><br> * Original signature : <code>int ioctl(int, int, char*)</code><br>
*/ */
public int ioctl(int d, int request, ByteBuffer data); public int ioctl(int d, int request, ByteBuffer data);
/** /**
* Put the state of FD into *TERMIOS_P.<br> * Put the state of FD into *TERMIOS_P.<br>
* *
* Original signature : <code>int tcgetattr(int, char*)</code><br> * Original signature : <code>int tcgetattr(int, char*)</code><br>
*/ */
public int tcgetattr(int fd, TermiosStruct termios_p); public int tcgetattr(int fd, TermiosStruct termios_p);
/** /**
* Set the state of FD to *TERMIOS_P.<br> * Set the state of FD to *TERMIOS_P.<br>
* *
* Values for OPTIONAL_ACTIONS (TCSA*) are in <bits/termios.h>.<br> * Values for OPTIONAL_ACTIONS (TCSA*) are in <bits/termios.h>.<br>
* *
* Original signature : <code>int tcsetattr(int, int, char*)</code><br> * Original signature : <code>int tcsetattr(int, int, char*)</code><br>
*/ */
public int tcsetattr(int fd, int optional_actions, TermiosStruct termios_p); public int tcsetattr(int fd, int optional_actions, TermiosStruct termios_p);
} }

View File

@ -15,55 +15,53 @@
*/ */
package dorkbox.util.input.posix; package dorkbox.util.input.posix;
import com.sun.jna.Structure;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import com.sun.jna.Structure;
@SuppressWarnings("ALL") @SuppressWarnings("ALL")
class TermiosStruct extends Structure { 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 * control characters
*/ */
public int c_iflag; public byte[] c_cc = new byte[32];
/**
* 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 * input speed
*/ */
public byte[] c_cc = new byte[32]; public int c_ispeed;
/**
* output speed
*/
public int c_ospeed;
/** public TermiosStruct() {}
* input speed
*/
public int c_ispeed;
/**
* output speed
*/
public int c_ospeed;
public TermiosStruct() { @Override
} protected List<String> getFieldOrder() {
return Arrays.asList(
@Override
protected List<String> getFieldOrder() {
return Arrays.asList(
"c_iflag", "c_iflag",
"c_oflag", "c_oflag",
"c_cflag", "c_cflag",
@ -71,7 +69,6 @@ class TermiosStruct extends Structure {
"c_line", "c_line",
"c_cc", "c_cc",
"c_ispeed", "c_ispeed",
"c_ospeed" "c_ospeed");
); }
}
} }

View File

@ -15,46 +15,45 @@
*/ */
package dorkbox.util.input.posix; package dorkbox.util.input.posix;
import com.sun.jna.Native;
import java.io.IOException; import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import com.sun.jna.Native;
import dorkbox.util.input.Encoding; import dorkbox.util.input.Encoding;
import dorkbox.util.input.Terminal; import dorkbox.util.input.Terminal;
/** /**
* Terminal that is used for unix platforms. Terminal initialization is handled via JNA and * Terminal that is used for unix platforms. Terminal initialization is handled via JNA and ioctl/tcgetattr/tcsetattr/cfmakeraw.
* ioctl/tcgetattr/tcsetattr/cfmakeraw.
* *
* This implementation should work for an reasonable POSIX system. * This implementation should work for an reasonable POSIX system.
*/ */
public class UnixTerminal extends Terminal { public class UnixTerminal extends Terminal {
private volatile TermiosStruct termInfoDefault = new TermiosStruct(); private volatile TermiosStruct termInfoDefault = new TermiosStruct();
private volatile TermiosStruct termInfo = new TermiosStruct(); private volatile TermiosStruct termInfo = new TermiosStruct();
private final Reader reader; private final Reader reader;
private final PosixTerminalControl term; private final PosixTerminalControl term;
private ByteBuffer windowSizeBuffer = ByteBuffer.allocate(8); private ByteBuffer windowSizeBuffer = ByteBuffer.allocate(8);
public UnixTerminal() throws Exception { public UnixTerminal() throws Exception {
String encoding = Encoding.get(); String encoding = Encoding.get();
this.reader = new InputStreamReader(System.in, encoding); 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 // save off the defaults
if (this.term.tcgetattr(0, this.termInfoDefault) != 0) { if (this.term.tcgetattr(0, this.termInfoDefault) != 0) {
throw new IOException("Failed to get terminal info"); throw new IOException("Failed to get terminal info");
}
} }
}
@Override @Override
public void init() throws IOException { public void init() throws IOException {
// COMPARABLE TO (from upstream) // COMPARABLE TO (from upstream)
//settings.set("-icanon min 1 -ixon"); //settings.set("-icanon min 1 -ixon");
@ -77,118 +76,118 @@ public class UnixTerminal extends Terminal {
// t->c_cc[VMIN] = 1; // t->c_cc[VMIN] = 1;
// t->c_cc[VTIME] = 0; // t->c_cc[VTIME] = 0;
if (this.term.tcgetattr(0, this.termInfo) != 0) { if (this.term.tcgetattr(0, this.termInfo) != 0) {
throw new IOException("Failed to get terminal info"); 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 * Restore the original terminal configuration, which can be used when shutting down the console reader. The ConsoleReader cannot be
* used after calling this method.
this.termInfo.c_lflag &= */
~PosixTerminalControl.ICANON; // DISABLE - canonical mode (pass chars straight through to terminal) @Override
// struct.c_lflag &= ~PosixTerminalControl.ISIG; // DISABLE - When any of the characters INTR, QUIT, SUSP, or DSUSP are received, generate the corresponding signal. public final void restore() throws IOException {
if (this.term.tcsetattr(0, PosixTerminalControl.TCSANOW, this.termInfoDefault) != 0) {
// 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. throw new IOException("Can not reset terminal to defaults");
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;
} }
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;
}
/** return (short) (0x000000FF & this.windowSizeBuffer.get(2) + (0x000000FF & this.windowSizeBuffer.get(3)) * 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;
} }
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 return (short) (0x000000FF & this.windowSizeBuffer.get(0) + (0x000000FF & this.windowSizeBuffer.get(1)) * 256);
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) { @Override
this.termInfo.c_lflag |= PosixTerminalControl.ECHO; // ENABLE Echo input characters. public final synchronized void setEchoEnabled(final boolean enabled) {
} else { // have to reget them, since flags change everything
this.termInfo.c_lflag &= ~PosixTerminalControl.ECHO; // DISABLE Echo input characters. 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) { // public final void disableInterruptCharacter() {
this.logger.error("Can not set terminal flags"); // // 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;
}
}
} }

View File

@ -23,66 +23,64 @@ import dorkbox.util.input.Terminal;
public class UnsupportedTerminal extends 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 int readerCount = -1;
private final InputStream in; private final InputStream in;
public UnsupportedTerminal() { public UnsupportedTerminal() {
this.in = System.in; this.in = System.in;
} }
@Override @Override
public final void init() throws IOException { public final void init() throws IOException {}
}
@Override @Override
public final void restore() { public final void restore() {}
}
@Override @Override
public final int getWidth() { public final int getWidth() {
return 0; return 0;
} }
@Override @Override
public final int getHeight() { public final int getHeight() {
return 0; return 0;
} }
@Override @Override
public final int read() { 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! // 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) // so, 'readerCount' is REALLY the index at which we return letters (until the whole string is returned)
if (this.readerCount == -1) { if (this.readerCount == -1) {
// we have to wait for more data. // we have to wait for more data.
try { try {
InputStream sysIn = this.in; InputStream sysIn = this.in;
int read; int read;
char asChar; char asChar;
this.buffer.clearSecure(); this.buffer.clearSecure();
while ((read = sysIn.read()) != -1) { while ((read = sysIn.read()) != -1) {
asChar = (char) read; asChar = (char) read;
if (asChar == '\n') { if (asChar == '\n') {
this.readerCount = this.buffer.position(); this.readerCount = this.buffer.position();
this.buffer.rewind(); this.buffer.rewind();
break; break;
} else { } else {
this.buffer.writeChar(asChar); this.buffer.writeChar(asChar);
} }
}
} catch (IOException ignored) {
}
} }
} catch (IOException ignored) {
}
}
// EACH thread will have it's own count! // EACH thread will have it's own count!
if (this.readerCount == this.buffer.position()) { if (this.readerCount == this.buffer.position()) {
this.readerCount = -1; this.readerCount = -1;
return '\n'; return '\n';
} else { } else {
return this.buffer.readChar(); return this.buffer.readChar();
}
} }
}
} }

View File

@ -12,47 +12,48 @@
package dorkbox.util.input.windows; package dorkbox.util.input.windows;
/** /**
* Console mode <p/> Constants copied <tt>wincon.h</tt>. * Console mode
* <p/>
* Constants copied <tt>wincon.h</tt>.
*/ */
public enum ConsoleMode { public enum ConsoleMode {
/** /**
* The ReadFile or ReadConsole function returns only when a carriage return character is read. If this mode is * The ReadFile or ReadConsole function returns only when a carriage return character is read. If this mode is disable, the functions
* disable, the functions return when one or more characters are available. * return when one or more characters are available.
*/ */
ENABLE_LINE_INPUT(2), ENABLE_LINE_INPUT(2),
/** /**
* Characters read by the ReadFile or ReadConsole function are written to the active screen buffer as they are read. * Characters read by the ReadFile or ReadConsole function are written to the active screen buffer as they are read. This mode can be
* This mode can be used only if the ENABLE_LINE_INPUT mode is also enabled. * used only if the ENABLE_LINE_INPUT mode is also enabled.
*/ */
ENABLE_ECHO_INPUT(4), 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 * 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
* ReadFile or ReadConsole, other control keys are processed by the system and are not returned in the ReadFile or * ReadConsole, other control keys are processed by the system and are not returned in the ReadFile or ReadConsole buffer. If the
* ReadConsole buffer. If the ENABLE_LINE_INPUT mode is also enabled, backspace, carriage return, and linefeed * ENABLE_LINE_INPUT mode is also enabled, backspace, carriage return, and linefeed characters are handled by the system.
* characters are handled by the system. */
*/ ENABLE_PROCESSED_INPUT(1),
ENABLE_PROCESSED_INPUT(1),
/** /**
* User interactions that change the size of the console screen buffer are reported in the console's input buffee. * User interactions that change the size of the console screen buffer are reported in the console's input buffee. Information about
* Information about these events can be read from the input buffer by applications using theReadConsoleInput * these events can be read from the input buffer by applications using theReadConsoleInput function, but not by those using ReadFile
* function, but not by those using ReadFile orReadConsole. * orReadConsole.
*/ */
ENABLE_WINDOW_INPUT(8), ENABLE_WINDOW_INPUT(8),
/** /**
* If the mouse pointer is within the borders of the console window and the window has the keyboard focus, mouse * If the mouse pointer is within the borders of the console window and the window has the keyboard focus, mouse events generated by
* events generated by mouse movement and button presses are placed in the input buffer. These events are discarded by * mouse movement and button presses are placed in the input buffer. These events are discarded by ReadFile or ReadConsole, even when
* ReadFile or ReadConsole, even when this mode is enabled. * this mode is enabled.
*/ */
ENABLE_MOUSE_INPUT(16),; ENABLE_MOUSE_INPUT(16), ;
public final int code; public final int code;
ConsoleMode(final int code) { ConsoleMode(final int code) {
this.code = code; this.code = code;
} }
} }

View File

@ -11,114 +11,115 @@
*/ */
package dorkbox.util.input.windows; 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.INPUT_RECORD;
import org.fusesource.jansi.internal.Kernel32.KEY_EVENT_RECORD; import org.fusesource.jansi.internal.Kernel32.KEY_EVENT_RECORD;
import org.fusesource.jansi.internal.WindowsSupport; import org.fusesource.jansi.internal.WindowsSupport;
import java.io.IOException;
import java.io.PrintStream;
import dorkbox.util.input.Terminal; import dorkbox.util.input.Terminal;
/** /**
* Terminal implementation for Microsoft Windows. Terminal initialization in {@link #init} is accomplished by calling * Terminal implementation for Microsoft Windows. Terminal initialization in {@link #init} is accomplished by calling the Win32 APIs <a
* the Win32 APIs <a href="http://msdn.microsoft.com/library/default.asp? url=/library/en-us/dllproc/base/setconsolemode.asp">SetConsoleMode</a> * href="http://msdn.microsoft.com/library/default.asp? url=/library/en-us/dllproc/base/setconsolemode.asp">SetConsoleMode</a> and <a
* and <a href="http://msdn.microsoft.com/library/default.asp? url=/library/en-us/dllproc/base/getconsolemode.asp">GetConsoleMode</a> * href="http://msdn.microsoft.com/library/default.asp? url=/library/en-us/dllproc/base/getconsolemode.asp">GetConsoleMode</a> to disable
* to disable character echoing. <p/> * character echoing.
* <p/>
* *
* @since 2.0 (customized) * @since 2.0 (customized)
*/ */
public class WindowsTerminal extends Terminal { public class WindowsTerminal extends Terminal {
private volatile int originalMode; private volatile int originalMode;
private final PrintStream out; private final PrintStream out;
public WindowsTerminal() { public WindowsTerminal() {
this.out = System.out; this.out = System.out;
} }
@Override @Override
public final void init() throws IOException { public final void init() throws IOException {
this.originalMode = WindowsSupport.getConsoleMode(); this.originalMode = WindowsSupport.getConsoleMode();
// Must set these four modes at the same time to make it work fine. // Must set these four modes at the same time to make it work fine.
WindowsSupport.setConsoleMode(this.originalMode | WindowsSupport.setConsoleMode(this.originalMode |
ConsoleMode.ENABLE_LINE_INPUT.code | ConsoleMode.ENABLE_LINE_INPUT.code |
ConsoleMode.ENABLE_ECHO_INPUT.code | ConsoleMode.ENABLE_ECHO_INPUT.code |
ConsoleMode.ENABLE_PROCESSED_INPUT.code | ConsoleMode.ENABLE_PROCESSED_INPUT.code |
ConsoleMode.ENABLE_WINDOW_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() { @Override
// this HOOKS the input event, and prevents it from going to the console "proper" public final int getWidth() {
try { int w = WindowsSupport.getWindowsTerminalWidth();
INPUT_RECORD[] events; return w < 1 ? DEFAULT_WIDTH : w;
while (true) { }
// we ALWAYS read until we have an event we care about!
events = WindowsSupport.readConsoleInput(1);
if (events != null) { @Override
for (int i = 0; i < events.length; i++) { public final int getHeight() {
KEY_EVENT_RECORD keyEvent = events[i].keyEvent; int h = WindowsSupport.getWindowsTerminalHeight();
//Log.trace(keyEvent.keyDown? "KEY_DOWN" : "KEY_UP", "key code:", keyEvent.keyCode, "char:", (long)keyEvent.uchar); return h < 1 ? DEFAULT_HEIGHT : h;
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; @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) { return input;
this.logger.error("Windows console input error: ", e);
} }
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;
}
} }