¿Cómo leer un solo carácter de la consola en Java (cuando el usuario lo escribe)?

110

¿Existe una manera fácil de leer un solo carácter desde la consola mientras el usuario lo escribe en Java? ¿Es posible? Probé con estos métodos, pero todos esperan a que el usuario presione la tecla Intro :

char tmp = (char) System.in.read();
char tmp = (char) new InputStreamReader(System.in).read ();
char tmp = (char) System.console().reader().read();           // Java 6

Estoy empezando a pensar que System.in no es consciente de la entrada del usuario hasta que se presiona enter .

Victor Hugo
fuente

Respuestas:

57

Lo que quiere hacer es poner la consola en modo "raw" (se omite la edición de línea y no se requiere tecla enter) en lugar del modo "cocido" (se requiere edición de línea con la tecla enter). En sistemas UNIX, el comando 'stty' puede modos de cambio.

Ahora, con respecto a Java ... consulte Entrada de consola sin bloqueo en Python y Java . Extracto:

Si su programa debe estar basado en consola, debe cambiar su terminal del modo de línea al modo de caracteres y recuerde restaurarlo antes de que se cierre el programa. No existe una forma portátil de hacer esto en todos los sistemas operativos.

Una de las sugerencias es utilizar JNI. Una vez más, eso no es muy portátil. Otra sugerencia al final del hilo, y en común con la publicación anterior, es considerar el uso de jCurses .

Chris W. Rea
fuente
4
JCurses tampoco es muy portátil ... Del README de JCurses: "JCurses consta de dos partes: la parte independiente de la plataforma y la parte dependiente de la plataforma, que consiste en una biblioteca compartida nativa que hace que las operaciones primitivas de entrada y salida estén disponibles para la primera parte . "
Ryan Fernandes
6
@RyanFernandes me suena bastante portátil: una sola herramienta que se puede ejecutar en múltiples sistemas (usando diferentes dependencias)
Antoniossss
25

Necesitas poner tu consola en modo raw. No hay una forma integrada de llegar allí independiente de la plataforma. Sin embargo, jCurses podría ser interesante.

En un sistema Unix, esto podría funcionar:

String[] cmd = {"/bin/sh", "-c", "stty raw </dev/tty"};
Runtime.getRuntime().exec(cmd).waitFor();

Por ejemplo, si desea tener en cuenta el tiempo entre pulsaciones de teclas, aquí tiene un código de muestra para llegar allí.

nes1983
fuente
Funcionó bien para mí bajo Linux
MrSmith42
4
También funcionó en Mac. Probablemente desee mencionar que stty cooked </dev/ttydebe ejecutarse cuando el programa necesite volver al modo de búfer y definitivamente antes de que el programa salga.
Kelvin
15

No existe una forma portátil de leer caracteres sin formato desde una consola Java.

Algunas soluciones alternativas dependientes de la plataforma se han presentado anteriormente. Pero para ser realmente portátil, tendría que abandonar el modo de consola y utilizar un modo de ventana, por ejemplo, AWT o Swing.

rustyx
fuente
22
No entiendo muy bien por qué, por ejemplo, Mono (o CLR) tiene el System.Console.ReadKeyque funciona en todas las plataformas. Java también distribuye JVM y JRE para cada plataforma con bibliotecas e implementaciones dependientes de la plataforma, así que eso no es excusa.
Martin Macak
13

Escribí una clase de Java RawConsoleInput que usa JNA para llamar a funciones del sistema operativo de Windows y Unix / Linux.

  • En Windows utiliza _kbhit()y _getwch()desde msvcrt.dll.
  • En Unix, se usa tcsetattr()para cambiar la consola al modo no canónico, System.in.available()para verificar si hay datos disponibles y System.in.read()leer bytes de la consola. A CharsetDecoderse utiliza para convertir bytes en caracteres.

Admite entrada sin bloqueo y modo de mezcla sin procesar y entrada de modo de línea normal.

Christian d'Heureuse
fuente
¿Qué tanto se ha probado o sometido a pruebas de estrés?
Financia la demanda de Monica
2
@QPaysTaxes La prueba de estrés es difícil para la entrada de la consola. Creo que, en este caso, sería más importante probarlo en varios entornos (diferentes versiones de Windows / Linux, 64/32 bits, Linux vía SSH, Telnet, puerto serie o consola de escritorio, etc.). Hasta ahora solo lo uso en mis herramientas de prueba privadas. Pero el código fuente es relativamente pequeño en comparación con otras soluciones (como JLine2 que usa Jansi). Así que no hay mucho que pueda salir mal. Lo escribí porque JLine2 no admite la entrada de un solo carácter sin bloqueo.
Christian d'Heureuse
Eso es lo que quise decir con prueba de estrés: probablemente sea la palabra incorrecta; culpa mía. De todos modos, ¡agradable! He robado ^ H ^ H ^ H ^ H ^ H ^ Lo incluí en un proyecto mío para la escuela y ayudó mucho.
Financia la demanda de Monica
Oye, esta clase se ve genial. Sin embargo: no puedo hacer que funcione ... ¿cómo se supone que debo usarlo? Me he encontrado con el bloqueo de System.in hasta que presiono CTRL + D (en Linux) y ahora leo sobre los modos de consola y similares. Creo que RawConsoleInput es lo que estoy buscando, pero ¿cómo lo uso?
Igor
@Igor Simplemente llame a RawConsoleInput.read (boolean) para leer un carácter del teclado. Está documentado en el código fuente (RawConsoleInput.java).
Christian d'Heureuse
8

Utilice jline3 :

Ejemplo:

Terminal terminal = TerminalBuilder.builder()
    .jna(true)
    .system(true)
    .build();

// raw mode means we get keypresses rather than line buffered input
terminal.enterRawMode();
reader = terminal .reader();
...
int read = reader.read();
....
reader.close();
terminal.close();
Vaina
fuente
Descubrí que las soluciones basadas en RawConsoleInput no funcionaban en MacOS High Sierra; sin embargo, esto funciona perfectamente.
RawToast
jline tiene prácticamente todo lo que necesita para crear una consola interactiva / sistema de terminal. Funciona muy bien en Linux. Para obtener un ejemplo más completo, consulte: github.com/jline/jline3/blob/master/builtins/src/test/java/org/… . Tiene autocompletar, historial, máscara de contraseña, etc.
lepe