Moved InputConsole to github (via private repo --mirror)
This commit is contained in:
parent
03728b9324
commit
576d0af40d
@ -103,6 +103,12 @@ Legal:
|
||||
|
||||
|
||||
|
||||
- JNA - Apache 2.0 License
|
||||
https://github.com/twall/jna
|
||||
Copyright (c) 2011 Timothy Wall
|
||||
|
||||
|
||||
|
||||
- LAN HostDiscovery from Apache Commons JCS - Apache 2.0 License
|
||||
https://issues.apache.org/jira/browse/JCS-40
|
||||
Copyright 2001-2014 The Apache Software Foundation.
|
||||
|
17
Dorkbox-Util/src/dorkbox/util/Keep.java
Normal file
17
Dorkbox-Util/src/dorkbox/util/Keep.java
Normal file
@ -0,0 +1,17 @@
|
||||
package dorkbox.util;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Marker annotation for proguard
|
||||
*
|
||||
*/
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
@Target({ElementType.TYPE})
|
||||
@Keep
|
||||
public @interface Keep {
|
||||
|
||||
}
|
@ -13,10 +13,10 @@ public class NamedThreadFactory implements ThreadFactory {
|
||||
/**
|
||||
* The stack size is arbitrary based on JVM implementation. Default is 0
|
||||
* 8k is the size of the android stack. Depending on the version of android, this can either change, or will always be 8k
|
||||
*
|
||||
*<p>
|
||||
* To be honest, 8k is pretty reasonable for an asynchronous/event based system (32bit) or 16k (64bit)
|
||||
* Setting the size MAY or MAY NOT have any effect!!!
|
||||
*
|
||||
* <p>
|
||||
* Stack size must be specified in bytes. Default is 8k
|
||||
*/
|
||||
public static int stackSizeForThreads = 8192;
|
||||
@ -26,13 +26,40 @@ public class NamedThreadFactory implements ThreadFactory {
|
||||
private final ThreadGroup group;
|
||||
private final String namePrefix;
|
||||
private final int threadPriority;
|
||||
private final boolean daemon;
|
||||
|
||||
public NamedThreadFactory(String poolNamePrefix, ThreadGroup group) {
|
||||
this(poolNamePrefix, group, Thread.MAX_PRIORITY);
|
||||
/**
|
||||
* Creates a DAEMON thread
|
||||
*/
|
||||
public NamedThreadFactory(String poolNamePrefix) {
|
||||
this(poolNamePrefix, Thread.currentThread().getThreadGroup(), Thread.MAX_PRIORITY, true);
|
||||
}
|
||||
|
||||
public NamedThreadFactory(String poolNamePrefix, boolean isDaemon) {
|
||||
this(poolNamePrefix, Thread.currentThread().getThreadGroup(), Thread.MAX_PRIORITY, isDaemon);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a DAEMON thread
|
||||
*/
|
||||
public NamedThreadFactory(String poolNamePrefix, ThreadGroup group) {
|
||||
this(poolNamePrefix, group, Thread.MAX_PRIORITY, true);
|
||||
}
|
||||
|
||||
public NamedThreadFactory(String poolNamePrefix, ThreadGroup group, boolean isDaemon) {
|
||||
this(poolNamePrefix, group, Thread.MAX_PRIORITY, isDaemon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DAEMON thread
|
||||
*/
|
||||
public NamedThreadFactory(String poolNamePrefix, int threadPriority) {
|
||||
this(poolNamePrefix, null, threadPriority);
|
||||
this(poolNamePrefix, threadPriority, true);
|
||||
}
|
||||
|
||||
public NamedThreadFactory(String poolNamePrefix, int threadPriority, boolean isDaemon) {
|
||||
this(poolNamePrefix, null, threadPriority, isDaemon);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -41,7 +68,8 @@ public class NamedThreadFactory implements ThreadFactory {
|
||||
* @param group the group this thread will belong to. If NULL, it will belong to the current thread group.
|
||||
* @param threadPriority Thread.MIN_PRIORITY, Thread.NORM_PRIORITY, Thread.MAX_PRIORITY
|
||||
*/
|
||||
public NamedThreadFactory(String poolNamePrefix, ThreadGroup group, int threadPriority) {
|
||||
public NamedThreadFactory(String poolNamePrefix, ThreadGroup group, int threadPriority, boolean isDaemon) {
|
||||
this.daemon = isDaemon;
|
||||
this.namePrefix = poolNamePrefix + '-' + poolId.incrementAndGet();
|
||||
if (group == null) {
|
||||
this.group = Thread.currentThread().getThreadGroup();
|
||||
@ -62,9 +90,7 @@ public class NamedThreadFactory implements ThreadFactory {
|
||||
// To be honest, 8k is pretty reasonable for an asynchronous/event based system (32bit) or 16k (64bit)
|
||||
// Setting the size MAY or MAY NOT have any effect!!!
|
||||
Thread t = new Thread(this.group, r, this.namePrefix + '-' + this.nextId.incrementAndGet(), stackSizeForThreads);
|
||||
if (!t.isDaemon()) {
|
||||
t.setDaemon(true);
|
||||
}
|
||||
t.setDaemon(this.daemon);
|
||||
if (t.getPriority() != this.threadPriority) {
|
||||
t.setPriority(this.threadPriority);
|
||||
}
|
||||
|
@ -1,38 +0,0 @@
|
||||
package dorkbox.util.input;
|
||||
|
||||
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;
|
||||
}
|
||||
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 <code>[language[_territory][.codeset][@modifier]]</code>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
@ -1,504 +0,0 @@
|
||||
package dorkbox.util.input;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.net.URL;
|
||||
import java.security.CodeSource;
|
||||
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;
|
||||
import dorkbox.util.input.posix.UnixTerminal;
|
||||
import dorkbox.util.input.unsupported.UnsupportedTerminal;
|
||||
import dorkbox.util.input.windows.WindowsTerminal;
|
||||
import dorkbox.util.objectPool.ObjectPool;
|
||||
import dorkbox.util.objectPool.ObjectPoolFactory;
|
||||
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];
|
||||
|
||||
/**
|
||||
* empty method to allow code to initialize the input console.
|
||||
*/
|
||||
public static void init() {
|
||||
}
|
||||
|
||||
// this is run by our init...
|
||||
{
|
||||
AnsiConsole.systemInstall();
|
||||
|
||||
Thread consoleThread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
consoleProxyReader.run();
|
||||
}
|
||||
});
|
||||
consoleThread.setDaemon(true);
|
||||
consoleThread.setName("Console Input Reader");
|
||||
|
||||
consoleThread.start();
|
||||
|
||||
// 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();
|
||||
|
||||
consoleProxyReader.shutdown0();
|
||||
}
|
||||
};
|
||||
shutdownThread.setName("Console Input Shutdown");
|
||||
Runtime.getRuntime().addShutdownHook(shutdownThread);
|
||||
}
|
||||
|
||||
/** return null if no data */
|
||||
public static final String readLine() {
|
||||
char[] line = consoleProxyReader.readLine0();
|
||||
return new String(line);
|
||||
}
|
||||
|
||||
/** return -1 if no data */
|
||||
public static final int read() {
|
||||
return consoleProxyReader.read0();
|
||||
}
|
||||
|
||||
/** return null if no data */
|
||||
public static final char[] readLinePassword() {
|
||||
return consoleProxyReader.readLinePassword0();
|
||||
}
|
||||
|
||||
public static InputStream getInputStream() {
|
||||
return new InputStream() {
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return consoleProxyReader.read0();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
consoleProxyReader.release0();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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 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) {
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
t.setEchoEnabled(true);
|
||||
|
||||
this.terminal = t;
|
||||
logger.debug("Created Terminal: {} ({}x{})", t.getClass().getSimpleName(), t.getWidth(), t.getHeight());
|
||||
}
|
||||
|
||||
// called when the JVM is shutting down.
|
||||
private void shutdown0() {
|
||||
synchronized (this.inputLockSingle) {
|
||||
this.inputLockSingle.notifyAll();
|
||||
}
|
||||
|
||||
synchronized (this.inputLockLine) {
|
||||
this.inputLockLine.notifyAll();
|
||||
}
|
||||
|
||||
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 final int read0() {
|
||||
Integer bufferCounter = this.threadBufferCounter.get();
|
||||
ObjectPoolHolder<ByteBuffer2> objectPoolHolder = this.readBuff.get();
|
||||
ByteBuffer2 buffer = null;
|
||||
|
||||
if (objectPoolHolder == null) {
|
||||
bufferCounter = 0;
|
||||
this.threadBufferCounter.set(bufferCounter);
|
||||
|
||||
ObjectPoolHolder<ByteBuffer2> holder = this.pool.take();
|
||||
buffer = holder.getValue();
|
||||
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 final 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 final 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 final 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.
|
||||
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);
|
||||
}
|
||||
|
||||
this.inputLockSingle.notifyAll();
|
||||
}
|
||||
|
||||
|
||||
// now to handle readLine stuff
|
||||
|
||||
// if we type a backspace key, swallow it + previous in READLINE. READCHAR will have it passed.
|
||||
if (asChar == '\b') {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 e) {
|
||||
}
|
||||
|
||||
// fall-back to unsupported
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public 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;
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
package dorkbox.util.input;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public abstract class Terminal {
|
||||
protected final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(getClass());
|
||||
|
||||
|
||||
public static final int DEFAULT_WIDTH = 80;
|
||||
public static final int DEFAULT_HEIGHT = 24;
|
||||
|
||||
private volatile boolean echoEnabled;
|
||||
|
||||
public Terminal() {
|
||||
}
|
||||
|
||||
public abstract void init() throws IOException;
|
||||
|
||||
public abstract void restore() throws IOException;
|
||||
|
||||
public void setEchoEnabled(boolean enabled) {
|
||||
this.echoEnabled = enabled;
|
||||
}
|
||||
|
||||
public boolean isEchoEnabled() {
|
||||
return this.echoEnabled;
|
||||
}
|
||||
|
||||
public abstract int getWidth();
|
||||
public abstract int getHeight();
|
||||
|
||||
/**
|
||||
* @return a character from whatever underlying input method the terminal has available.
|
||||
*/
|
||||
public abstract int read();
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
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 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";
|
||||
}
|
@ -1,342 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
package dorkbox.util.input.posix;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CoderResult;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
import java.nio.charset.MalformedInputException;
|
||||
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).
|
||||
*/
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public class InputStreamReader extends Reader {
|
||||
private InputStream in;
|
||||
|
||||
private static final int BUFFER_SIZE = 8192;
|
||||
|
||||
private boolean endOfInput = false;
|
||||
|
||||
String encoding;
|
||||
|
||||
CharsetDecoder decoder;
|
||||
|
||||
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();
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
package dorkbox.util.input.posix;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import com.sun.jna.Library;
|
||||
|
||||
public interface PosixTerminalControl extends Library {
|
||||
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 */
|
||||
|
||||
|
||||
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;
|
||||
|
||||
// MAGIC!
|
||||
public static final int TIOCGWINSZ = System.getProperty("os.name").equalsIgnoreCase("linux") ? 0x5413 : 1074295912;
|
||||
|
||||
public int open(String path, int flags);
|
||||
|
||||
public int close(int fd);
|
||||
|
||||
/**
|
||||
* Original signature : <code>int ioctl(int, int, char*)</code><br>
|
||||
*/
|
||||
public int ioctl(int d, int request, ByteBuffer data);
|
||||
|
||||
/**
|
||||
* Put the state of FD into *TERMIOS_P.<br>
|
||||
*
|
||||
* Original signature : <code>int tcgetattr(int, char*)</code><br>
|
||||
*/
|
||||
public int tcgetattr(int fd, TermiosStruct termios_p);
|
||||
|
||||
/**
|
||||
* Set the state of FD to *TERMIOS_P.<br>
|
||||
*
|
||||
* Values for OPTIONAL_ACTIONS (TCSA*) are in <bits/termios.h>.<br>
|
||||
*
|
||||
* Original signature : <code>int tcsetattr(int, int, char*)</code><br>
|
||||
*/
|
||||
public int tcsetattr(int fd, int optional_actions, TermiosStruct termios_p);
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
package dorkbox.util.input.posix;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.sun.jna.Structure;
|
||||
|
||||
public 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;
|
||||
|
||||
/** control characters */
|
||||
public byte[] c_cc = new byte[32];
|
||||
|
||||
/** input speed */
|
||||
public int c_ispeed;
|
||||
/** output speed */
|
||||
public int c_ospeed;
|
||||
|
||||
public TermiosStruct() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"c_iflag",
|
||||
"c_oflag",
|
||||
"c_cflag",
|
||||
"c_lflag",
|
||||
"c_line",
|
||||
"c_cc",
|
||||
"c_ispeed",
|
||||
"c_ospeed"
|
||||
);
|
||||
}
|
||||
}
|
@ -1,183 +0,0 @@
|
||||
package dorkbox.util.input.posix;
|
||||
|
||||
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.
|
||||
*
|
||||
* 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 Reader reader;
|
||||
|
||||
private PosixTerminalControl term;
|
||||
private ByteBuffer windowSizeBuffer = ByteBuffer.allocate(8);
|
||||
|
||||
|
||||
public UnixTerminal() throws Exception {
|
||||
String encoding = Encoding.get();
|
||||
this.reader = new InputStreamReader(System.in, encoding);
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() throws IOException {
|
||||
|
||||
// COMPARABLE TO (from upstream)
|
||||
//settings.set("-icanon min 1 -ixon");
|
||||
//settings.set("dsusp undef");
|
||||
|
||||
/*
|
||||
* NOT done in constructor, since our unit test DOES NOT use this!
|
||||
*
|
||||
* Set the console to be character-buffered instead of line-buffered.
|
||||
* Allow ctrl-s and ctrl-q keypress to be used (as forward search)
|
||||
*/
|
||||
|
||||
// raw mode
|
||||
// t->c_iflag &= ~(IMAXBEL|IXOFF|INPCK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON|IGNPAR);
|
||||
// t->c_iflag |= IGNBRK;
|
||||
// t->c_oflag &= ~OPOST;
|
||||
// t->c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|ICANON|ISIG|IEXTEN|NOFLSH|TOSTOP|PENDIN);
|
||||
// t->c_cflag &= ~(CSIZE|PARENB);
|
||||
// t->c_cflag |= CS8|CREAD;
|
||||
// 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");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
short columns = (short)(0x000000FF &this.windowSizeBuffer.get(2) + (0x000000FF & this.windowSizeBuffer.get(3)) * 256);
|
||||
return columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
short rows = (short)(0x000000FF &this.windowSizeBuffer.get(0) + (0x000000FF & this.windowSizeBuffer.get(1)) * 256);
|
||||
return rows;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
package dorkbox.util.input.unsupported;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import dorkbox.util.bytes.ByteBuffer2;
|
||||
import dorkbox.util.input.Terminal;
|
||||
|
||||
public class UnsupportedTerminal extends Terminal {
|
||||
|
||||
private ByteBuffer2 buffer = new ByteBuffer2(8, -1);
|
||||
|
||||
private int readerCount = -1;
|
||||
private InputStream in;
|
||||
|
||||
public UnsupportedTerminal() {
|
||||
this.in = System.in;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void init() throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void restore() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getWidth() {
|
||||
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!
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
} catch (IOException e1) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// EACH thread will have it's own count!
|
||||
if (this.readerCount == this.buffer.position()) {
|
||||
this.readerCount = -1;
|
||||
return '\n';
|
||||
} else {
|
||||
char c = this.buffer.readChar();
|
||||
return c;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
package dorkbox.util.input.windows;
|
||||
|
||||
/**
|
||||
* Console mode
|
||||
* <p/>
|
||||
* Constants copied <tt>wincon.h</tt>.
|
||||
*/
|
||||
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),
|
||||
|
||||
/**
|
||||
* 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),
|
||||
|
||||
/**
|
||||
* 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),
|
||||
;
|
||||
|
||||
|
||||
public final int code;
|
||||
|
||||
ConsoleMode(final int code) {
|
||||
this.code = code;
|
||||
}
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2002-2012, the original author or authors.
|
||||
*
|
||||
* This software is distributable under the BSD license. See the terms of the
|
||||
* BSD license in the documentation provided with this software.
|
||||
*
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
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 dorkbox.util.input.Terminal;
|
||||
|
||||
/**
|
||||
* Terminal implementation for Microsoft Windows. Terminal initialization in
|
||||
* {@link #init} is accomplished by calling the Win32 APIs <a
|
||||
* href="http://msdn.microsoft.com/library/default.asp?
|
||||
* url=/library/en-us/dllproc/base/setconsolemode.asp">SetConsoleMode</a> and
|
||||
* <a href="http://msdn.microsoft.com/library/default.asp?
|
||||
* url=/library/en-us/dllproc/base/getconsolemode.asp">GetConsoleMode</a> to
|
||||
* disable character echoing.
|
||||
* <p/>
|
||||
* @since 2.0 (customized)
|
||||
*/
|
||||
public class WindowsTerminal extends Terminal {
|
||||
private volatile int originalMode;
|
||||
private final PrintStream out;
|
||||
|
||||
public WindowsTerminal() {
|
||||
this.out = System.out;
|
||||
}
|
||||
|
||||
@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 |
|
||||
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;
|
||||
}
|
||||
|
||||
private final int readInput() {
|
||||
// this HOOKS the input event, and prevents it from going to the console "proper"
|
||||
try {
|
||||
INPUT_RECORD[] events = null;
|
||||
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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user