mirror of
https://github.com/JakeWharton/mosaic.git
synced 2025-10-27 12:05:01 +08:00
Rewrite native input handling
This will allow out-of-band events to come in, such as a resize from SIGWINCH, or focus and resize from Windows' console events in a future change.
This commit is contained in:
@ -1,16 +1,10 @@
|
|||||||
public final class com/jakewharton/mosaic/terminal/StdinReader : java/lang/AutoCloseable {
|
public final class com/jakewharton/mosaic/terminal/TerminalReader : java/lang/AutoCloseable {
|
||||||
public fun close ()V
|
public fun close ()V
|
||||||
public final fun interrupt ()V
|
public final fun getEvents ()Lkotlinx/coroutines/channels/ReceiveChannel;
|
||||||
public final fun read ([BII)I
|
|
||||||
public final fun readWithTimeout ([BIII)I
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class com/jakewharton/mosaic/terminal/TerminalParser {
|
|
||||||
public fun <init> (Lcom/jakewharton/mosaic/terminal/StdinReader;)V
|
|
||||||
public final fun debugNext ()Lkotlin/Pair;
|
|
||||||
public final fun getKittyDisambiguateEscapeCodes ()Z
|
public final fun getKittyDisambiguateEscapeCodes ()Z
|
||||||
public final fun getXtermExtendedUtf8Mouse ()Z
|
public final fun getXtermExtendedUtf8Mouse ()Z
|
||||||
public final fun next ()Lcom/jakewharton/mosaic/terminal/event/Event;
|
public final fun interrupt ()V
|
||||||
|
public final fun runParseLoop ()V
|
||||||
public final fun setKittyDisambiguateEscapeCodes (Z)V
|
public final fun setKittyDisambiguateEscapeCodes (Z)V
|
||||||
public final fun setXtermExtendedUtf8Mouse (Z)V
|
public final fun setXtermExtendedUtf8Mouse (Z)V
|
||||||
}
|
}
|
||||||
@ -18,7 +12,8 @@ public final class com/jakewharton/mosaic/terminal/TerminalParser {
|
|||||||
public final class com/jakewharton/mosaic/terminal/Tty {
|
public final class com/jakewharton/mosaic/terminal/Tty {
|
||||||
public static final field INSTANCE Lcom/jakewharton/mosaic/terminal/Tty;
|
public static final field INSTANCE Lcom/jakewharton/mosaic/terminal/Tty;
|
||||||
public static final fun enableRawMode ()Ljava/lang/AutoCloseable;
|
public static final fun enableRawMode ()Ljava/lang/AutoCloseable;
|
||||||
public static final fun stdinReader ()Lcom/jakewharton/mosaic/terminal/StdinReader;
|
public static final fun terminalReader (Z)Lcom/jakewharton/mosaic/terminal/TerminalReader;
|
||||||
|
public static synthetic fun terminalReader$default (ZILjava/lang/Object;)Lcom/jakewharton/mosaic/terminal/TerminalReader;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class com/jakewharton/mosaic/terminal/event/BracketedPasteEvent : com/jakewharton/mosaic/terminal/event/Event {
|
public final class com/jakewharton/mosaic/terminal/event/BracketedPasteEvent : com/jakewharton/mosaic/terminal/event/Event {
|
||||||
@ -29,6 +24,15 @@ public final class com/jakewharton/mosaic/terminal/event/BracketedPasteEvent : c
|
|||||||
public fun toString ()Ljava/lang/String;
|
public fun toString ()Ljava/lang/String;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class com/jakewharton/mosaic/terminal/event/DebugEvent : com/jakewharton/mosaic/terminal/event/Event {
|
||||||
|
public fun <init> (Lcom/jakewharton/mosaic/terminal/event/Event;[B)V
|
||||||
|
public fun equals (Ljava/lang/Object;)Z
|
||||||
|
public final fun getBytes ()[B
|
||||||
|
public final fun getEvent ()Lcom/jakewharton/mosaic/terminal/event/Event;
|
||||||
|
public fun hashCode ()I
|
||||||
|
public fun toString ()Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
public final class com/jakewharton/mosaic/terminal/event/DecModeReportEvent : com/jakewharton/mosaic/terminal/event/Event {
|
public final class com/jakewharton/mosaic/terminal/event/DecModeReportEvent : com/jakewharton/mosaic/terminal/event/Event {
|
||||||
public fun <init> (ILcom/jakewharton/mosaic/terminal/event/DecModeReportEvent$Setting;)V
|
public fun <init> (ILcom/jakewharton/mosaic/terminal/event/DecModeReportEvent$Setting;)V
|
||||||
public fun equals (Ljava/lang/Object;)Z
|
public fun equals (Ljava/lang/Object;)Z
|
||||||
|
|||||||
@ -21,6 +21,19 @@ final class com.jakewharton.mosaic.terminal.event/BracketedPasteEvent : com.jake
|
|||||||
final fun toString(): kotlin/String // com.jakewharton.mosaic.terminal.event/BracketedPasteEvent.toString|toString(){}[0]
|
final fun toString(): kotlin/String // com.jakewharton.mosaic.terminal.event/BracketedPasteEvent.toString|toString(){}[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class com.jakewharton.mosaic.terminal.event/DebugEvent : com.jakewharton.mosaic.terminal.event/Event { // com.jakewharton.mosaic.terminal.event/DebugEvent|null[0]
|
||||||
|
constructor <init>(com.jakewharton.mosaic.terminal.event/Event, kotlin/ByteArray) // com.jakewharton.mosaic.terminal.event/DebugEvent.<init>|<init>(com.jakewharton.mosaic.terminal.event.Event;kotlin.ByteArray){}[0]
|
||||||
|
|
||||||
|
final val bytes // com.jakewharton.mosaic.terminal.event/DebugEvent.bytes|{}bytes[0]
|
||||||
|
final fun <get-bytes>(): kotlin/ByteArray // com.jakewharton.mosaic.terminal.event/DebugEvent.bytes.<get-bytes>|<get-bytes>(){}[0]
|
||||||
|
final val event // com.jakewharton.mosaic.terminal.event/DebugEvent.event|{}event[0]
|
||||||
|
final fun <get-event>(): com.jakewharton.mosaic.terminal.event/Event // com.jakewharton.mosaic.terminal.event/DebugEvent.event.<get-event>|<get-event>(){}[0]
|
||||||
|
|
||||||
|
final fun equals(kotlin/Any?): kotlin/Boolean // com.jakewharton.mosaic.terminal.event/DebugEvent.equals|equals(kotlin.Any?){}[0]
|
||||||
|
final fun hashCode(): kotlin/Int // com.jakewharton.mosaic.terminal.event/DebugEvent.hashCode|hashCode(){}[0]
|
||||||
|
final fun toString(): kotlin/String // com.jakewharton.mosaic.terminal.event/DebugEvent.toString|toString(){}[0]
|
||||||
|
}
|
||||||
|
|
||||||
final class com.jakewharton.mosaic.terminal.event/DecModeReportEvent : com.jakewharton.mosaic.terminal.event/Event { // com.jakewharton.mosaic.terminal.event/DecModeReportEvent|null[0]
|
final class com.jakewharton.mosaic.terminal.event/DecModeReportEvent : com.jakewharton.mosaic.terminal.event/Event { // com.jakewharton.mosaic.terminal.event/DecModeReportEvent|null[0]
|
||||||
constructor <init>(kotlin/Int, com.jakewharton.mosaic.terminal.event/DecModeReportEvent.Setting) // com.jakewharton.mosaic.terminal.event/DecModeReportEvent.<init>|<init>(kotlin.Int;com.jakewharton.mosaic.terminal.event.DecModeReportEvent.Setting){}[0]
|
constructor <init>(kotlin/Int, com.jakewharton.mosaic.terminal.event/DecModeReportEvent.Setting) // com.jakewharton.mosaic.terminal.event/DecModeReportEvent.<init>|<init>(kotlin.Int;com.jakewharton.mosaic.terminal.event.DecModeReportEvent.Setting){}[0]
|
||||||
|
|
||||||
@ -300,28 +313,23 @@ final class com.jakewharton.mosaic.terminal.event/XtermPixelSizeEvent : com.jake
|
|||||||
final fun toString(): kotlin/String // com.jakewharton.mosaic.terminal.event/XtermPixelSizeEvent.toString|toString(){}[0]
|
final fun toString(): kotlin/String // com.jakewharton.mosaic.terminal.event/XtermPixelSizeEvent.toString|toString(){}[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
final class com.jakewharton.mosaic.terminal/StdinReader : kotlin/AutoCloseable { // com.jakewharton.mosaic.terminal/StdinReader|null[0]
|
final class com.jakewharton.mosaic.terminal/TerminalReader : kotlin/AutoCloseable { // com.jakewharton.mosaic.terminal/TerminalReader|null[0]
|
||||||
final fun close() // com.jakewharton.mosaic.terminal/StdinReader.close|close(){}[0]
|
final val events // com.jakewharton.mosaic.terminal/TerminalReader.events|{}events[0]
|
||||||
final fun interrupt() // com.jakewharton.mosaic.terminal/StdinReader.interrupt|interrupt(){}[0]
|
final fun <get-events>(): kotlinx.coroutines.channels/ReceiveChannel<com.jakewharton.mosaic.terminal.event/Event> // com.jakewharton.mosaic.terminal/TerminalReader.events.<get-events>|<get-events>(){}[0]
|
||||||
final fun read(kotlin/ByteArray, kotlin/Int, kotlin/Int): kotlin/Int // com.jakewharton.mosaic.terminal/StdinReader.read|read(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0]
|
|
||||||
final fun readWithTimeout(kotlin/ByteArray, kotlin/Int, kotlin/Int, kotlin/Int): kotlin/Int // com.jakewharton.mosaic.terminal/StdinReader.readWithTimeout|readWithTimeout(kotlin.ByteArray;kotlin.Int;kotlin.Int;kotlin.Int){}[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
final class com.jakewharton.mosaic.terminal/TerminalParser { // com.jakewharton.mosaic.terminal/TerminalParser|null[0]
|
final var kittyDisambiguateEscapeCodes // com.jakewharton.mosaic.terminal/TerminalReader.kittyDisambiguateEscapeCodes|{}kittyDisambiguateEscapeCodes[0]
|
||||||
constructor <init>(com.jakewharton.mosaic.terminal/StdinReader) // com.jakewharton.mosaic.terminal/TerminalParser.<init>|<init>(com.jakewharton.mosaic.terminal.StdinReader){}[0]
|
final fun <get-kittyDisambiguateEscapeCodes>(): kotlin/Boolean // com.jakewharton.mosaic.terminal/TerminalReader.kittyDisambiguateEscapeCodes.<get-kittyDisambiguateEscapeCodes>|<get-kittyDisambiguateEscapeCodes>(){}[0]
|
||||||
|
final fun <set-kittyDisambiguateEscapeCodes>(kotlin/Boolean) // com.jakewharton.mosaic.terminal/TerminalReader.kittyDisambiguateEscapeCodes.<set-kittyDisambiguateEscapeCodes>|<set-kittyDisambiguateEscapeCodes>(kotlin.Boolean){}[0]
|
||||||
|
final var xtermExtendedUtf8Mouse // com.jakewharton.mosaic.terminal/TerminalReader.xtermExtendedUtf8Mouse|{}xtermExtendedUtf8Mouse[0]
|
||||||
|
final fun <get-xtermExtendedUtf8Mouse>(): kotlin/Boolean // com.jakewharton.mosaic.terminal/TerminalReader.xtermExtendedUtf8Mouse.<get-xtermExtendedUtf8Mouse>|<get-xtermExtendedUtf8Mouse>(){}[0]
|
||||||
|
final fun <set-xtermExtendedUtf8Mouse>(kotlin/Boolean) // com.jakewharton.mosaic.terminal/TerminalReader.xtermExtendedUtf8Mouse.<set-xtermExtendedUtf8Mouse>|<set-xtermExtendedUtf8Mouse>(kotlin.Boolean){}[0]
|
||||||
|
|
||||||
final var kittyDisambiguateEscapeCodes // com.jakewharton.mosaic.terminal/TerminalParser.kittyDisambiguateEscapeCodes|{}kittyDisambiguateEscapeCodes[0]
|
final fun close() // com.jakewharton.mosaic.terminal/TerminalReader.close|close(){}[0]
|
||||||
final fun <get-kittyDisambiguateEscapeCodes>(): kotlin/Boolean // com.jakewharton.mosaic.terminal/TerminalParser.kittyDisambiguateEscapeCodes.<get-kittyDisambiguateEscapeCodes>|<get-kittyDisambiguateEscapeCodes>(){}[0]
|
final fun interrupt() // com.jakewharton.mosaic.terminal/TerminalReader.interrupt|interrupt(){}[0]
|
||||||
final fun <set-kittyDisambiguateEscapeCodes>(kotlin/Boolean) // com.jakewharton.mosaic.terminal/TerminalParser.kittyDisambiguateEscapeCodes.<set-kittyDisambiguateEscapeCodes>|<set-kittyDisambiguateEscapeCodes>(kotlin.Boolean){}[0]
|
final fun runParseLoop() // com.jakewharton.mosaic.terminal/TerminalReader.runParseLoop|runParseLoop(){}[0]
|
||||||
final var xtermExtendedUtf8Mouse // com.jakewharton.mosaic.terminal/TerminalParser.xtermExtendedUtf8Mouse|{}xtermExtendedUtf8Mouse[0]
|
|
||||||
final fun <get-xtermExtendedUtf8Mouse>(): kotlin/Boolean // com.jakewharton.mosaic.terminal/TerminalParser.xtermExtendedUtf8Mouse.<get-xtermExtendedUtf8Mouse>|<get-xtermExtendedUtf8Mouse>(){}[0]
|
|
||||||
final fun <set-xtermExtendedUtf8Mouse>(kotlin/Boolean) // com.jakewharton.mosaic.terminal/TerminalParser.xtermExtendedUtf8Mouse.<set-xtermExtendedUtf8Mouse>|<set-xtermExtendedUtf8Mouse>(kotlin.Boolean){}[0]
|
|
||||||
|
|
||||||
final fun debugNext(): kotlin/Pair<com.jakewharton.mosaic.terminal.event/Event, kotlin/ByteArray> // com.jakewharton.mosaic.terminal/TerminalParser.debugNext|debugNext(){}[0]
|
|
||||||
final fun next(): com.jakewharton.mosaic.terminal.event/Event // com.jakewharton.mosaic.terminal/TerminalParser.next|next(){}[0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final object com.jakewharton.mosaic.terminal/Tty { // com.jakewharton.mosaic.terminal/Tty|null[0]
|
final object com.jakewharton.mosaic.terminal/Tty { // com.jakewharton.mosaic.terminal/Tty|null[0]
|
||||||
final fun enableRawMode(): kotlin/AutoCloseable // com.jakewharton.mosaic.terminal/Tty.enableRawMode|enableRawMode(){}[0]
|
final fun enableRawMode(): kotlin/AutoCloseable // com.jakewharton.mosaic.terminal/Tty.enableRawMode|enableRawMode(){}[0]
|
||||||
final fun stdinReader(): com.jakewharton.mosaic.terminal/StdinReader // com.jakewharton.mosaic.terminal/Tty.stdinReader|stdinReader(){}[0]
|
final fun terminalReader(kotlin/Boolean = ...): com.jakewharton.mosaic.terminal/TerminalReader // com.jakewharton.mosaic.terminal/Tty.terminalReader|terminalReader(kotlin.Boolean){}[0]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,17 +38,23 @@ kotlin {
|
|||||||
}
|
}
|
||||||
if (it.name.endsWith("Test")) {
|
if (it.name.endsWith("Test")) {
|
||||||
languageSettings {
|
languageSettings {
|
||||||
|
optIn('com.jakewharton.mosaic.terminal.TestApi')
|
||||||
optIn('kotlin.ExperimentalStdlibApi')
|
optIn('kotlin.ExperimentalStdlibApi')
|
||||||
optIn('kotlinx.coroutines.DelicateCoroutinesApi')
|
optIn('kotlinx.coroutines.DelicateCoroutinesApi')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
commonMain {
|
||||||
|
dependencies {
|
||||||
|
api libs.kotlinx.coroutines.core
|
||||||
|
}
|
||||||
|
}
|
||||||
commonTest {
|
commonTest {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation libs.kotlin.test
|
implementation libs.kotlin.test
|
||||||
|
implementation libs.kotlinx.coroutines.test
|
||||||
implementation libs.kotlinx.io
|
implementation libs.kotlinx.io
|
||||||
implementation libs.kotlinx.coroutines.core
|
|
||||||
implementation libs.assertk
|
implementation libs.assertk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,4 +4,6 @@
|
|||||||
#define likely(x) __builtin_expect(!!(x), 1)
|
#define likely(x) __builtin_expect(!!(x), 1)
|
||||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||||
|
|
||||||
|
#define UNUSED __attribute__((unused))
|
||||||
|
|
||||||
#endif // CUTILS_H
|
#endif // CUTILS_H
|
||||||
|
|||||||
@ -15,6 +15,7 @@ typedef struct stdinReaderImpl {
|
|||||||
int pipe[2];
|
int pipe[2];
|
||||||
fd_set fds;
|
fd_set fds;
|
||||||
int nfds;
|
int nfds;
|
||||||
|
platformEventHandler *handler;
|
||||||
} stdinReaderImpl;
|
} stdinReaderImpl;
|
||||||
|
|
||||||
typedef struct stdinWriterImpl {
|
typedef struct stdinWriterImpl {
|
||||||
@ -22,7 +23,7 @@ typedef struct stdinWriterImpl {
|
|||||||
stdinReader *reader;
|
stdinReader *reader;
|
||||||
} stdinWriterImpl;
|
} stdinWriterImpl;
|
||||||
|
|
||||||
stdinReaderResult stdinReader_initWithFd(int stdinFd) {
|
stdinReaderResult stdinReader_initWithFd(int stdinFd, platformEventHandler *handler) {
|
||||||
stdinReaderResult result = {};
|
stdinReaderResult result = {};
|
||||||
|
|
||||||
stdinReaderImpl *reader = calloc(1, sizeof(stdinReaderImpl));
|
stdinReaderImpl *reader = calloc(1, sizeof(stdinReaderImpl));
|
||||||
@ -40,6 +41,7 @@ stdinReaderResult stdinReader_initWithFd(int stdinFd) {
|
|||||||
// TODO Consider forcing the writer pipe to always be lower than this pipe.
|
// TODO Consider forcing the writer pipe to always be lower than this pipe.
|
||||||
// If we did this, we could always assume pipe[0] + 1 is the value for nfds.
|
// If we did this, we could always assume pipe[0] + 1 is the value for nfds.
|
||||||
reader->nfds = ((stdinFd > reader->pipe[0]) ? stdinFd : reader->pipe[0]) + 1;
|
reader->nfds = ((stdinFd > reader->pipe[0]) ? stdinFd : reader->pipe[0]) + 1;
|
||||||
|
reader->handler = handler;
|
||||||
|
|
||||||
result.reader = reader;
|
result.reader = reader;
|
||||||
|
|
||||||
@ -51,8 +53,8 @@ stdinReaderResult stdinReader_initWithFd(int stdinFd) {
|
|||||||
goto ret;
|
goto ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
stdinReaderResult stdinReader_init() {
|
stdinReaderResult stdinReader_init(platformEventHandler *handler) {
|
||||||
return stdinReader_initWithFd(STDIN_FILENO);
|
return stdinReader_initWithFd(STDIN_FILENO, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
stdinRead stdinReader_readInternal(
|
stdinRead stdinReader_readInternal(
|
||||||
@ -139,7 +141,7 @@ platformError stdinReader_free(stdinReader *reader) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
stdinWriterResult stdinWriter_init() {
|
stdinWriterResult stdinWriter_init(platformEventHandler *handler) {
|
||||||
stdinWriterResult result = {};
|
stdinWriterResult result = {};
|
||||||
|
|
||||||
stdinWriterImpl *writer = calloc(1, sizeof(stdinWriterImpl));
|
stdinWriterImpl *writer = calloc(1, sizeof(stdinWriterImpl));
|
||||||
@ -153,7 +155,7 @@ stdinWriterResult stdinWriter_init() {
|
|||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
stdinReaderResult readerResult = stdinReader_initWithFd(writer->pipe[0]);
|
stdinReaderResult readerResult = stdinReader_initWithFd(writer->pipe[0], handler);
|
||||||
if (unlikely(readerResult.error)) {
|
if (unlikely(readerResult.error)) {
|
||||||
result.error = readerResult.error;
|
result.error = readerResult.error;
|
||||||
goto err;
|
goto err;
|
||||||
@ -189,6 +191,23 @@ platformError stdinWriter_write(stdinWriter *writer, void *buffer, int count) {
|
|||||||
return errno;
|
return errno;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void stdinWriter_focusEvent(stdinWriter *writer UNUSED, bool focused UNUSED) {
|
||||||
|
// Focus events are delivered through VT sequences.
|
||||||
|
}
|
||||||
|
|
||||||
|
void stdinWriter_keyEvent(stdinWriter *writer UNUSED) {
|
||||||
|
// Key events are delivered through VT sequences.
|
||||||
|
}
|
||||||
|
|
||||||
|
void stdinWriter_mouseEvent(stdinWriter *writer UNUSED) {
|
||||||
|
// Mouse events are delivered through VT sequences.
|
||||||
|
}
|
||||||
|
|
||||||
|
void stdinWriter_resizeEvent(stdinWriter *writer, int columns, int rows, int width, int height) {
|
||||||
|
platformEventHandler *handler = writer->reader->handler;
|
||||||
|
handler->onResize(handler->opaque, columns, rows, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
platformError stdinWriter_free(stdinWriter *writer) {
|
platformError stdinWriter_free(stdinWriter *writer) {
|
||||||
int *pipe = writer->pipe;
|
int *pipe = writer->pipe;
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
typedef struct stdinReaderImpl {
|
typedef struct stdinReaderImpl {
|
||||||
HANDLE waitHandles[2];
|
HANDLE waitHandles[2];
|
||||||
HANDLE readHandle;
|
HANDLE readHandle;
|
||||||
|
platformEventHandler *handler;
|
||||||
} stdinReaderImpl;
|
} stdinReaderImpl;
|
||||||
|
|
||||||
typedef struct stdinWriterImpl {
|
typedef struct stdinWriterImpl {
|
||||||
@ -17,7 +18,11 @@ typedef struct stdinWriterImpl {
|
|||||||
stdinReader *reader;
|
stdinReader *reader;
|
||||||
} stdinWriterImpl;
|
} stdinWriterImpl;
|
||||||
|
|
||||||
stdinReaderResult stdinReader_initWithHandle(HANDLE stdinRead, HANDLE stdinWait) {
|
stdinReaderResult stdinReader_initWithHandle(
|
||||||
|
HANDLE stdinRead,
|
||||||
|
HANDLE stdinWait,
|
||||||
|
platformEventHandler *handler
|
||||||
|
) {
|
||||||
stdinReaderResult result = {};
|
stdinReaderResult result = {};
|
||||||
|
|
||||||
stdinReaderImpl *reader = calloc(1, sizeof(stdinReaderImpl));
|
stdinReaderImpl *reader = calloc(1, sizeof(stdinReaderImpl));
|
||||||
@ -30,15 +35,17 @@ stdinReaderResult stdinReader_initWithHandle(HANDLE stdinRead, HANDLE stdinWait)
|
|||||||
result.error = GetLastError();
|
result.error = GetLastError();
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
reader->readHandle = stdinRead;
|
|
||||||
reader->waitHandles[0] = stdinWait;
|
|
||||||
|
|
||||||
HANDLE interruptEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
HANDLE interruptEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||||
if (unlikely(interruptEvent == NULL)) {
|
if (unlikely(interruptEvent == NULL)) {
|
||||||
result.error = GetLastError();
|
result.error = GetLastError();
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reader->waitHandles[0] = stdinWait;
|
||||||
reader->waitHandles[1] = interruptEvent;
|
reader->waitHandles[1] = interruptEvent;
|
||||||
|
reader->readHandle = stdinRead;
|
||||||
|
reader->handler = handler;
|
||||||
|
|
||||||
result.reader = reader;
|
result.reader = reader;
|
||||||
|
|
||||||
@ -50,9 +57,9 @@ stdinReaderResult stdinReader_initWithHandle(HANDLE stdinRead, HANDLE stdinWait)
|
|||||||
goto ret;
|
goto ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
stdinReaderResult stdinReader_init() {
|
stdinReaderResult stdinReader_init(platformEventHandler *handler) {
|
||||||
HANDLE h = GetStdHandle(STD_INPUT_HANDLE);
|
HANDLE h = GetStdHandle(STD_INPUT_HANDLE);
|
||||||
return stdinReader_initWithHandle(h, h);
|
return stdinReader_initWithHandle(h, h, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
stdinRead stdinReader_read(
|
stdinRead stdinReader_read(
|
||||||
@ -107,7 +114,7 @@ platformError stdinReader_free(stdinReader *reader) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
stdinWriterResult stdinWriter_init() {
|
stdinWriterResult stdinWriter_init(platformEventHandler *handler) {
|
||||||
stdinWriterResult result = {};
|
stdinWriterResult result = {};
|
||||||
|
|
||||||
stdinWriterImpl *writer = calloc(1, sizeof(stdinWriterImpl));
|
stdinWriterImpl *writer = calloc(1, sizeof(stdinWriterImpl));
|
||||||
@ -128,7 +135,7 @@ stdinWriterResult stdinWriter_init() {
|
|||||||
}
|
}
|
||||||
writer->eventHandle = writeEvent;
|
writer->eventHandle = writeEvent;
|
||||||
|
|
||||||
stdinReaderResult readerResult = stdinReader_initWithHandle(writer->readHandle, writer->eventHandle);
|
stdinReaderResult readerResult = stdinReader_initWithHandle(writer->readHandle, writer->eventHandle, handler);
|
||||||
if (unlikely(readerResult.error)) {
|
if (unlikely(readerResult.error)) {
|
||||||
result.error = readerResult.error;
|
result.error = readerResult.error;
|
||||||
goto err;
|
goto err;
|
||||||
@ -160,6 +167,26 @@ platformError stdinWriter_write(stdinWriter *writer, void *buffer, int count) {
|
|||||||
return GetLastError();
|
return GetLastError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void stdinWriter_focusEvent(stdinWriter *writer, bool focused) {
|
||||||
|
platformEventHandler *handler = writer->reader->handler;
|
||||||
|
handler->onFocus(handler->opaque, focused);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stdinWriter_keyEvent(stdinWriter *writer) {
|
||||||
|
platformEventHandler *handler = writer->reader->handler;
|
||||||
|
handler->onKey(handler->opaque);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stdinWriter_mouseEvent(stdinWriter *writer) {
|
||||||
|
platformEventHandler *handler = writer->reader->handler;
|
||||||
|
handler->onMouse(handler->opaque);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stdinWriter_resizeEvent(stdinWriter *writer, int columns, int rows, int width, int height) {
|
||||||
|
platformEventHandler *handler = writer->reader->handler;
|
||||||
|
handler->onResize(handler->opaque, columns, rows, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
platformError stdinWriter_free(stdinWriter *writer) {
|
platformError stdinWriter_free(stdinWriter *writer) {
|
||||||
DWORD result = 0;
|
DWORD result = 0;
|
||||||
if (unlikely(CloseHandle(writer->eventHandle) == 0)) {
|
if (unlikely(CloseHandle(writer->eventHandle) == 0)) {
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
#ifndef MOSAIC_H
|
#ifndef MOSAIC_H
|
||||||
#define MOSAIC_H
|
#define MOSAIC_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
#if defined(__APPLE__) || defined(__linux__)
|
#if defined(__APPLE__) || defined(__linux__)
|
||||||
|
|
||||||
#include <termios.h>
|
#include <termios.h>
|
||||||
@ -44,15 +46,33 @@ typedef struct stdinRead {
|
|||||||
platformError error;
|
platformError error;
|
||||||
} stdinRead;
|
} stdinRead;
|
||||||
|
|
||||||
stdinReaderResult stdinReader_init();
|
typedef void PlatformEventHandlerOnRead(void *opaque);
|
||||||
|
typedef void PlatformEventHandlerOnFocus(void *opaque, bool focused);
|
||||||
|
typedef void PlatformEventHandlerOnKey(void *opaque); // TODO params
|
||||||
|
typedef void PlatformEventHandlerOnMouse(void *opaque); // TODO params
|
||||||
|
typedef void PlatformEventHandlerOnResize(void *opaque, int columns, int rows, int width, int height);
|
||||||
|
|
||||||
|
typedef struct platformEventHandler {
|
||||||
|
void *opaque;
|
||||||
|
PlatformEventHandlerOnFocus *onFocus;
|
||||||
|
PlatformEventHandlerOnKey *onKey;
|
||||||
|
PlatformEventHandlerOnMouse *onMouse;
|
||||||
|
PlatformEventHandlerOnResize *onResize;
|
||||||
|
} platformEventHandler;
|
||||||
|
|
||||||
|
stdinReaderResult stdinReader_init(platformEventHandler *handler);
|
||||||
stdinRead stdinReader_read(stdinReader *reader, void *buffer, int count);
|
stdinRead stdinReader_read(stdinReader *reader, void *buffer, int count);
|
||||||
stdinRead stdinReader_readWithTimeout(stdinReader *reader, void *buffer, int count, int timeoutMillis);
|
stdinRead stdinReader_readWithTimeout(stdinReader *reader, void *buffer, int count, int timeoutMillis);
|
||||||
platformError stdinReader_interrupt(stdinReader* reader);
|
platformError stdinReader_interrupt(stdinReader* reader);
|
||||||
platformError stdinReader_free(stdinReader *reader);
|
platformError stdinReader_free(stdinReader *reader);
|
||||||
|
|
||||||
stdinWriterResult stdinWriter_init();
|
stdinWriterResult stdinWriter_init(platformEventHandler *handler);
|
||||||
stdinReader *stdinWriter_getReader(stdinWriter *writer);
|
stdinReader *stdinWriter_getReader(stdinWriter *writer);
|
||||||
platformError stdinWriter_write(stdinWriter *writer, void *buffer, int count);
|
platformError stdinWriter_write(stdinWriter *writer, void *buffer, int count);
|
||||||
|
void stdinWriter_focusEvent(stdinWriter *writer, bool focused);
|
||||||
|
void stdinWriter_keyEvent(stdinWriter *writer);
|
||||||
|
void stdinWriter_mouseEvent(stdinWriter *writer);
|
||||||
|
void stdinWriter_resizeEvent(stdinWriter *writer, int columns, int rows, int width, int height);
|
||||||
platformError stdinWriter_free(stdinWriter *writer);
|
platformError stdinWriter_free(stdinWriter *writer);
|
||||||
|
|
||||||
#endif // MOSAIC_H
|
#endif // MOSAIC_H
|
||||||
|
|||||||
@ -0,0 +1,26 @@
|
|||||||
|
package com.jakewharton.mosaic.terminal
|
||||||
|
|
||||||
|
import com.jakewharton.mosaic.terminal.event.Event
|
||||||
|
import com.jakewharton.mosaic.terminal.event.FocusEvent
|
||||||
|
import com.jakewharton.mosaic.terminal.event.ResizeEvent
|
||||||
|
import kotlinx.coroutines.channels.SendChannel
|
||||||
|
|
||||||
|
internal class PlatformEventHandler(
|
||||||
|
private val events: SendChannel<Event>,
|
||||||
|
) {
|
||||||
|
fun onFocus(focused: Boolean) {
|
||||||
|
events.trySend(FocusEvent(focused))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onKey() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onMouse() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onResize(columns: Int, rows: Int, width: Int, height: Int) {
|
||||||
|
events.trySend(ResizeEvent(columns, rows, width, height))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
package com.jakewharton.mosaic.terminal
|
package com.jakewharton.mosaic.terminal
|
||||||
|
|
||||||
import com.jakewharton.mosaic.terminal.event.BracketedPasteEvent
|
import com.jakewharton.mosaic.terminal.event.BracketedPasteEvent
|
||||||
|
import com.jakewharton.mosaic.terminal.event.DebugEvent
|
||||||
import com.jakewharton.mosaic.terminal.event.DecModeReportEvent
|
import com.jakewharton.mosaic.terminal.event.DecModeReportEvent
|
||||||
import com.jakewharton.mosaic.terminal.event.Event
|
import com.jakewharton.mosaic.terminal.event.Event
|
||||||
import com.jakewharton.mosaic.terminal.event.FocusEvent
|
import com.jakewharton.mosaic.terminal.event.FocusEvent
|
||||||
@ -20,17 +21,32 @@ import com.jakewharton.mosaic.terminal.event.TerminalVersionEvent
|
|||||||
import com.jakewharton.mosaic.terminal.event.UnknownEvent
|
import com.jakewharton.mosaic.terminal.event.UnknownEvent
|
||||||
import com.jakewharton.mosaic.terminal.event.XtermCharacterSizeEvent
|
import com.jakewharton.mosaic.terminal.event.XtermCharacterSizeEvent
|
||||||
import com.jakewharton.mosaic.terminal.event.XtermPixelSizeEvent
|
import com.jakewharton.mosaic.terminal.event.XtermPixelSizeEvent
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.channels.ReceiveChannel
|
||||||
|
|
||||||
private const val BufferSize = 8 * 1024
|
private const val BufferSize = 8 * 1024
|
||||||
private const val BareEscapeDisambiguationReadTimeoutMillis = 100
|
private const val BareEscapeDisambiguationReadTimeoutMillis = 100
|
||||||
|
|
||||||
public class TerminalParser(
|
public class TerminalReader internal constructor(
|
||||||
private val stdinReader: StdinReader,
|
private val platformInput: PlatformInput,
|
||||||
) {
|
events: Channel<Event>,
|
||||||
|
private val emitDebugEvents: Boolean,
|
||||||
|
) : AutoCloseable {
|
||||||
private val buffer = ByteArray(BufferSize)
|
private val buffer = ByteArray(BufferSize)
|
||||||
private var offset = 0
|
private var offset = 0
|
||||||
private var limit = 0
|
private var limit = 0
|
||||||
|
|
||||||
|
@TestApi
|
||||||
|
internal fun copyBuffer() = buffer.copyOfRange(offset, limit)
|
||||||
|
|
||||||
|
@TestApi
|
||||||
|
internal fun platformInput() = platformInput
|
||||||
|
|
||||||
|
private val _events = events
|
||||||
|
|
||||||
|
/** Events read as a result of calls to [tryReadEvents]. */
|
||||||
|
public val events: ReceiveChannel<Event> get() = _events
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicate whether Kitty's
|
* Indicate whether Kitty's
|
||||||
* [escape code disambiguation](https://sw.kovidgoyal.net/kitty/keyboard-protocol/#disambiguate-escape-codes)
|
* [escape code disambiguation](https://sw.kovidgoyal.net/kitty/keyboard-protocol/#disambiguate-escape-codes)
|
||||||
@ -57,77 +73,83 @@ public class TerminalParser(
|
|||||||
public var xtermExtendedUtf8Mouse: Boolean = false
|
public var xtermExtendedUtf8Mouse: Boolean = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A version of [next] which also returns the bytes that produced the event.
|
* Perform a blocking read from stdin to try and parse events. Calls to this function are not
|
||||||
|
* guaranteed to read an event, nor are they guaranteed to read only one event. Events
|
||||||
|
* which are read will be placed into [events].
|
||||||
*
|
*
|
||||||
* **WARNING** This function is expensive, and should only be used for debugging.
|
* It is expected that this function will be called repeatedly in a loop.
|
||||||
|
*
|
||||||
|
* @return False if returning due to [interrupt] being called. True means some data was read,
|
||||||
|
* but not necessarily that any events were put into the [events] channel. This could be because
|
||||||
|
* not enough bytes were available to parse the entire event, for example.
|
||||||
*/
|
*/
|
||||||
public fun debugNext(): Pair<Event, ByteArray> {
|
public fun runParseLoop() {
|
||||||
// Move any existing data to index 0 of the buffer. This will ensure we can capture all the
|
|
||||||
// bytes consumed (even across multiple reads) since the original offset will always be 0.
|
|
||||||
buffer.copyInto(buffer, 0, startIndex = offset, endIndex = limit)
|
|
||||||
limit = limit - offset
|
|
||||||
offset = 0
|
|
||||||
|
|
||||||
val event = next()
|
|
||||||
val bytes = buffer.copyOfRange(0, offset)
|
|
||||||
return event to bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun next(): Event {
|
|
||||||
val buffer = buffer
|
val buffer = buffer
|
||||||
var offset = offset
|
|
||||||
var limit = limit
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (offset < limit) {
|
if (offset < limit) {
|
||||||
parse(buffer, offset, limit)?.let { event ->
|
val event = tryParse(buffer, offset, limit)
|
||||||
return event
|
if (event != null) {
|
||||||
|
_events.trySend(event)
|
||||||
|
if (!emitDebugEvents) continue
|
||||||
|
|
||||||
|
// In debug event mode, parsing starts at index 0 of the buffer (see below). Leverage
|
||||||
|
// this to capture the consumed bytes by looking at where the next parse would start.
|
||||||
|
val eventBytes = buffer.copyOfRange(0, offset)
|
||||||
|
_events.trySend(DebugEvent(event, eventBytes))
|
||||||
|
|
||||||
|
// Move remaining data to the start of the buffer to maintain the parse from 0 invariant.
|
||||||
|
buffer.copyInto(buffer, 0, startIndex = offset, endIndex = limit)
|
||||||
|
limit -= offset
|
||||||
|
offset = 0
|
||||||
|
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Underflow! Copy any data to start of buffer in preparation for a read.
|
// Underflow! Copy any data to start of buffer in preparation for a read.
|
||||||
buffer.copyInto(buffer, 0, startIndex = offset, endIndex = limit)
|
buffer.copyInto(buffer, 0, startIndex = offset, endIndex = limit)
|
||||||
|
limit -= offset
|
||||||
// Do not write the new limit to the member property because the read code below will.
|
|
||||||
limit = limit - offset
|
|
||||||
|
|
||||||
offset = 0
|
offset = 0
|
||||||
this.offset = 0
|
|
||||||
|
|
||||||
if (kittyDisambiguateEscapeCodes || limit != 1 || buffer[0] != 0x1B.toByte()) {
|
if (kittyDisambiguateEscapeCodes || limit != 1 || buffer[0] != 0x1B.toByte()) {
|
||||||
// Common case: we are using the Kitty keyboard protocol to disambiguate escape keys, or
|
// Common case: we are using the Kitty keyboard protocol to disambiguate escape keys, or
|
||||||
// the buffer contains anything other than a bare escape. Do a normal read for more data.
|
// the buffer contains anything other than a bare escape. Do a normal read for more data.
|
||||||
val read = stdinReader.read(buffer, limit, BufferSize - limit)
|
val read = platformInput.read(buffer, limit, BufferSize - limit)
|
||||||
if (read == -1) break
|
if (read == -1) break // EOF
|
||||||
|
if (read == 0) return // Interrupt
|
||||||
|
|
||||||
limit += read
|
limit += read
|
||||||
this.limit = limit
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, perform a quick read to see if we have any more bytes. This will allow us to
|
// Otherwise, perform a quick read to see if we have any more bytes. This will allow us to
|
||||||
// determine whether the bare escape was truly a legacy keyboard escape event, or just the
|
// determine whether the bare escape was truly a legacy keyboard escape event, or just the
|
||||||
// start of some other escape sequence.
|
// start of some other escape sequence.
|
||||||
val read = stdinReader.readWithTimeout(
|
val read = platformInput.readWithTimeout(
|
||||||
buffer,
|
buffer,
|
||||||
1,
|
1,
|
||||||
BufferSize - 1,
|
BufferSize - 1,
|
||||||
BareEscapeDisambiguationReadTimeoutMillis,
|
BareEscapeDisambiguationReadTimeoutMillis,
|
||||||
)
|
)
|
||||||
if (read == 0) {
|
if (read == -1) break
|
||||||
|
|
||||||
|
limit = if (read == 0) {
|
||||||
|
_events.trySend(KeyboardEvent(0x1B))
|
||||||
// We know the offset is 0, so resetting the limit effectively consumes the byte.
|
// We know the offset is 0, so resetting the limit effectively consumes the byte.
|
||||||
this.limit = 0
|
0
|
||||||
return KeyboardEvent(0x1B)
|
} else {
|
||||||
} else if (read == -1) {
|
read + 1
|
||||||
break
|
|
||||||
}
|
}
|
||||||
limit += read
|
|
||||||
this.limit = limit
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw RuntimeException("stdin eof")
|
if (limit > 0) {
|
||||||
|
_events.trySend(UnknownEvent(buffer.copyOfRange(0, limit)))
|
||||||
|
}
|
||||||
|
_events.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parse(buffer: ByteArray, start: Int, limit: Int): Event? {
|
private fun tryParse(buffer: ByteArray, start: Int, limit: Int): Event? {
|
||||||
val b1 = buffer[start].toInt() and 0xff
|
val b1 = buffer[start].toInt() and 0xff
|
||||||
if (b1 == 0x1B) {
|
if (b1 == 0x1B) {
|
||||||
val b2Index = start + 1
|
val b2Index = start + 1
|
||||||
@ -797,4 +819,17 @@ public class TerminalParser(
|
|||||||
return handler(b3Index, stIndex)
|
return handler(b3Index, stIndex)
|
||||||
?: UnknownEvent(buffer.copyOfRange(start, end))
|
?: UnknownEvent(buffer.copyOfRange(start, end))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public fun interrupt() {
|
||||||
|
platformInput.interrupt()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free the resources associated with this reader.
|
||||||
|
*
|
||||||
|
* This call can be omitted if your process is exiting.
|
||||||
|
*/
|
||||||
|
override fun close() {
|
||||||
|
platformInput.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
package com.jakewharton.mosaic.terminal
|
||||||
|
|
||||||
|
import kotlin.annotation.AnnotationTarget.CLASS
|
||||||
|
import kotlin.annotation.AnnotationTarget.FUNCTION
|
||||||
|
import kotlin.annotation.AnnotationTarget.PROPERTY
|
||||||
|
|
||||||
|
@RequiresOptIn
|
||||||
|
@Target(CLASS, FUNCTION, PROPERTY)
|
||||||
|
internal annotation class TestApi
|
||||||
@ -1,5 +1,7 @@
|
|||||||
package com.jakewharton.mosaic.terminal
|
package com.jakewharton.mosaic.terminal
|
||||||
|
|
||||||
|
import com.jakewharton.mosaic.terminal.event.DebugEvent
|
||||||
|
|
||||||
public expect object Tty {
|
public expect object Tty {
|
||||||
/**
|
/**
|
||||||
* Save the current terminal settings and enter "raw" mode.
|
* Save the current terminal settings and enter "raw" mode.
|
||||||
@ -15,22 +17,26 @@ public expect object Tty {
|
|||||||
* In addition to the flags required for entering "raw" mode, on POSIX-compliant platforms,
|
* In addition to the flags required for entering "raw" mode, on POSIX-compliant platforms,
|
||||||
* this function will change the standard input stream to block indefinitely until a minimum
|
* this function will change the standard input stream to block indefinitely until a minimum
|
||||||
* of 1 byte is available to read. This allows the reader thread to fully be suspended rather
|
* of 1 byte is available to read. This allows the reader thread to fully be suspended rather
|
||||||
* than consuming CPU. Use [stdinReader] to read in a manner that can still be interrupted.
|
* than consuming CPU. Use [terminalReader] to read in a manner that can still be interrupted.
|
||||||
*/
|
*/
|
||||||
public fun enableRawMode(): AutoCloseable
|
public fun enableRawMode(): AutoCloseable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a [StdinReader] which will read from this process' stdin stream while also
|
* Create a [TerminalReader] which will read from this process' stdin stream while also
|
||||||
* supporting interruption.
|
* supporting interruption.
|
||||||
*
|
*
|
||||||
* Use with [enableRawMode] to read input byte-by-byte.
|
* Use with [enableRawMode] to read input byte-by-byte.
|
||||||
|
*
|
||||||
|
* @param emitDebugEvents When true, each event sent to [TerminalReader.events] will be followed
|
||||||
|
* by a [DebugEvent] that contains the original event and the bytes which produced it.
|
||||||
*/
|
*/
|
||||||
public fun stdinReader(): StdinReader
|
public fun terminalReader(emitDebugEvents: Boolean = false): TerminalReader
|
||||||
|
|
||||||
internal fun stdinWriter(): StdinWriter
|
@TestApi
|
||||||
|
internal fun stdinWriter(emitDebugEvents: Boolean = false): StdinWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
public expect class StdinReader : AutoCloseable {
|
internal expect class PlatformInput : AutoCloseable {
|
||||||
/**
|
/**
|
||||||
* Read up to [count] bytes into [buffer] at [offset]. The number of bytes read will be returned.
|
* Read up to [count] bytes into [buffer] at [offset]. The number of bytes read will be returned.
|
||||||
* 0 will be returned if [interrupt] is called while waiting for input. -1 will be returned if
|
* 0 will be returned if [interrupt] is called while waiting for input. -1 will be returned if
|
||||||
@ -38,7 +44,7 @@ public expect class StdinReader : AutoCloseable {
|
|||||||
*
|
*
|
||||||
* @see readWithTimeout
|
* @see readWithTimeout
|
||||||
*/
|
*/
|
||||||
public fun read(buffer: ByteArray, offset: Int, count: Int): Int
|
fun read(buffer: ByteArray, offset: Int, count: Int): Int
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read up to [count] bytes into [buffer] at [offset]. The number of bytes read will be returned.
|
* Read up to [count] bytes into [buffer] at [offset]. The number of bytes read will be returned.
|
||||||
@ -50,10 +56,10 @@ public expect class StdinReader : AutoCloseable {
|
|||||||
* value is not validated.
|
* value is not validated.
|
||||||
* @see read
|
* @see read
|
||||||
*/
|
*/
|
||||||
public fun readWithTimeout(buffer: ByteArray, offset: Int, count: Int, timeoutMillis: Int): Int
|
fun readWithTimeout(buffer: ByteArray, offset: Int, count: Int, timeoutMillis: Int): Int
|
||||||
|
|
||||||
/** Signal blocking calls to [read] to wake up and return 0. */
|
/** Signal blocking calls to [read] to wake up and return 0. */
|
||||||
public fun interrupt()
|
fun interrupt()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Free the resources associated with this reader.
|
* Free the resources associated with this reader.
|
||||||
@ -63,13 +69,19 @@ public expect class StdinReader : AutoCloseable {
|
|||||||
override fun close()
|
override fun close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestApi
|
||||||
internal expect class StdinWriter : AutoCloseable {
|
internal expect class StdinWriter : AutoCloseable {
|
||||||
val reader: StdinReader
|
val reader: TerminalReader
|
||||||
|
|
||||||
// TODO Take ByteString once it migrates to stdlib,
|
// TODO Take ByteString once it migrates to stdlib,
|
||||||
// or if Sink/RawSink migrates expose that as a val.
|
// or if Sink/RawSink migrates expose that as a val.
|
||||||
// https://github.com/Kotlin/kotlinx-io/issues/354
|
// https://github.com/Kotlin/kotlinx-io/issues/354
|
||||||
fun write(buffer: ByteArray)
|
fun write(buffer: ByteArray)
|
||||||
|
|
||||||
|
fun focusEvent(focused: Boolean)
|
||||||
|
fun keyEvent()
|
||||||
|
fun mouseEvent()
|
||||||
|
fun resizeEvent(columns: Int, rows: Int, width: Int, height: Int)
|
||||||
|
|
||||||
override fun close()
|
override fun close()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,6 +21,13 @@ public class UnknownEvent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Poko
|
||||||
|
public class DebugEvent(
|
||||||
|
public val event: Event,
|
||||||
|
// TODO ByteString once it moves into the stdlib.
|
||||||
|
@ReadArrayContent public val bytes: ByteArray,
|
||||||
|
) : Event
|
||||||
|
|
||||||
@Poko
|
@Poko
|
||||||
public class KeyboardEvent(
|
public class KeyboardEvent(
|
||||||
public val codepoint: Int,
|
public val codepoint: Int,
|
||||||
|
|||||||
@ -1,16 +1,34 @@
|
|||||||
package com.jakewharton.mosaic.terminal
|
package com.jakewharton.mosaic.terminal
|
||||||
|
|
||||||
|
import assertk.assertThat
|
||||||
|
import assertk.assertions.isEqualTo
|
||||||
|
import com.jakewharton.mosaic.terminal.event.Event
|
||||||
import kotlin.test.AfterTest
|
import kotlin.test.AfterTest
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.IO
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
||||||
abstract class BaseTerminalParserTest {
|
abstract class BaseTerminalParserTest {
|
||||||
internal val writer = Tty.stdinWriter()
|
internal val writer = Tty.stdinWriter()
|
||||||
internal val parser = TerminalParser(writer.reader)
|
internal val parser = writer.reader
|
||||||
|
private val runLoop = GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
parser.runParseLoop()
|
||||||
|
}
|
||||||
|
|
||||||
@AfterTest fun after() {
|
@AfterTest fun after() = runTest {
|
||||||
|
parser.interrupt()
|
||||||
|
runLoop.join()
|
||||||
writer.close()
|
writer.close()
|
||||||
|
assertThat(parser.copyBuffer().toHexString()).isEqualTo("")
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun StdinWriter.writeHex(hex: String) {
|
internal fun StdinWriter.writeHex(hex: String) {
|
||||||
write(hex.hexToByteArray())
|
write(hex.hexToByteArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal suspend fun TerminalReader.next(): Event {
|
||||||
|
return events.receive()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import kotlinx.coroutines.launch
|
|||||||
|
|
||||||
class StdinReaderTest {
|
class StdinReaderTest {
|
||||||
private val writer = Tty.stdinWriter()
|
private val writer = Tty.stdinWriter()
|
||||||
private val reader = writer.reader
|
private val reader = writer.reader.platformInput()
|
||||||
|
|
||||||
@AfterTest fun after() {
|
@AfterTest fun after() {
|
||||||
reader.close()
|
reader.close()
|
||||||
|
|||||||
@ -4,14 +4,15 @@ import assertk.assertThat
|
|||||||
import assertk.assertions.isEqualTo
|
import assertk.assertions.isEqualTo
|
||||||
import com.jakewharton.mosaic.terminal.event.BracketedPasteEvent
|
import com.jakewharton.mosaic.terminal.event.BracketedPasteEvent
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
||||||
class TerminalParserCsiBracketedPasteEventTest : BaseTerminalParserTest() {
|
class TerminalParserCsiBracketedPasteEventTest : BaseTerminalParserTest() {
|
||||||
@Test fun pasteStart() {
|
@Test fun pasteStart() = runTest {
|
||||||
writer.writeHex("1b5b3230307e")
|
writer.writeHex("1b5b3230307e")
|
||||||
assertThat(parser.next()).isEqualTo(BracketedPasteEvent(start = true))
|
assertThat(parser.next()).isEqualTo(BracketedPasteEvent(start = true))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun pasteEnd() {
|
@Test fun pasteEnd() = runTest {
|
||||||
writer.writeHex("1b5b3230317e")
|
writer.writeHex("1b5b3230317e")
|
||||||
assertThat(parser.next()).isEqualTo(BracketedPasteEvent(start = false))
|
assertThat(parser.next()).isEqualTo(BracketedPasteEvent(start = false))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,9 +10,10 @@ import com.jakewharton.mosaic.terminal.event.DecModeReportEvent.Setting.Reset
|
|||||||
import com.jakewharton.mosaic.terminal.event.DecModeReportEvent.Setting.Set
|
import com.jakewharton.mosaic.terminal.event.DecModeReportEvent.Setting.Set
|
||||||
import com.jakewharton.mosaic.terminal.event.UnknownEvent
|
import com.jakewharton.mosaic.terminal.event.UnknownEvent
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
||||||
class TerminalParserCsiDecModeReportEventTest : BaseTerminalParserTest() {
|
class TerminalParserCsiDecModeReportEventTest : BaseTerminalParserTest() {
|
||||||
@Test fun settings() {
|
@Test fun settings() = runTest {
|
||||||
writer.writeHex("1b5b3f313030343b302479")
|
writer.writeHex("1b5b3f313030343b302479")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
DecModeReportEvent(
|
DecModeReportEvent(
|
||||||
@ -54,7 +55,7 @@ class TerminalParserCsiDecModeReportEventTest : BaseTerminalParserTest() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun minimal() {
|
@Test fun minimal() = runTest {
|
||||||
writer.writeHex("1b5b3f313b302479")
|
writer.writeHex("1b5b3f313b302479")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
DecModeReportEvent(
|
DecModeReportEvent(
|
||||||
@ -64,56 +65,56 @@ class TerminalParserCsiDecModeReportEventTest : BaseTerminalParserTest() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun unknownSetting() {
|
@Test fun unknownSetting() = runTest {
|
||||||
writer.writeHex("1b5b313030343b352479")
|
writer.writeHex("1b5b313030343b352479")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5b313030343b352479".hexToByteArray()),
|
UnknownEvent("1b5b313030343b352479".hexToByteArray()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun noQuestion() {
|
@Test fun noQuestion() = runTest {
|
||||||
writer.writeHex("1b5b313030343b302479")
|
writer.writeHex("1b5b313030343b302479")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5b313030343b302479".hexToByteArray()),
|
UnknownEvent("1b5b313030343b302479".hexToByteArray()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun noDollar() {
|
@Test fun noDollar() = runTest {
|
||||||
writer.writeHex("1b5b3f313030343b3079")
|
writer.writeHex("1b5b3f313030343b3079")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5b3f313030343b3079".hexToByteArray()),
|
UnknownEvent("1b5b3f313030343b3079".hexToByteArray()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun noMode() {
|
@Test fun noMode() = runTest {
|
||||||
writer.writeHex("1b5b3f3b3130302479")
|
writer.writeHex("1b5b3f3b3130302479")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5b3f3b3130302479".hexToByteArray()),
|
UnknownEvent("1b5b3f3b3130302479".hexToByteArray()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun nonDigitMode() {
|
@Test fun nonDigitMode() = runTest {
|
||||||
writer.writeHex("1b5b3f31302d32343b302479")
|
writer.writeHex("1b5b3f31302d32343b302479")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5b3f31302d32343b302479".hexToByteArray()),
|
UnknownEvent("1b5b3f31302d32343b302479".hexToByteArray()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun noSetting() {
|
@Test fun noSetting() = runTest {
|
||||||
writer.writeHex("1b5b3f313030343b2479")
|
writer.writeHex("1b5b3f313030343b2479")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5b3f313030343b2479".hexToByteArray()),
|
UnknownEvent("1b5b3f313030343b2479".hexToByteArray()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun nonDigitSetting() {
|
@Test fun nonDigitSetting() = runTest {
|
||||||
writer.writeHex("1b5b3f313030343b312d322479")
|
writer.writeHex("1b5b3f313030343b312d322479")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5b3f313030343b312d322479".hexToByteArray()),
|
UnknownEvent("1b5b3f313030343b312d322479".hexToByteArray()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun noSemicolon() {
|
@Test fun noSemicolon() = runTest {
|
||||||
writer.writeHex("1b5b3f313030342479")
|
writer.writeHex("1b5b3f313030342479")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5b3f313030342479".hexToByteArray()),
|
UnknownEvent("1b5b3f313030342479".hexToByteArray()),
|
||||||
|
|||||||
@ -4,14 +4,15 @@ import assertk.assertThat
|
|||||||
import assertk.assertions.isEqualTo
|
import assertk.assertions.isEqualTo
|
||||||
import com.jakewharton.mosaic.terminal.event.FocusEvent
|
import com.jakewharton.mosaic.terminal.event.FocusEvent
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
||||||
class TerminalParserCsiFocusEventTest : BaseTerminalParserTest() {
|
class TerminalParserCsiFocusEventTest : BaseTerminalParserTest() {
|
||||||
@Test fun focusedTrue() {
|
@Test fun focusedTrue() = runTest {
|
||||||
writer.writeHex("1b5b49")
|
writer.writeHex("1b5b49")
|
||||||
assertThat(parser.next()).isEqualTo(FocusEvent(focused = true))
|
assertThat(parser.next()).isEqualTo(FocusEvent(focused = true))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun focusedFalse() {
|
@Test fun focusedFalse() = runTest {
|
||||||
writer.writeHex("1b5b4f")
|
writer.writeHex("1b5b4f")
|
||||||
assertThat(parser.next()).isEqualTo(FocusEvent(focused = false))
|
assertThat(parser.next()).isEqualTo(FocusEvent(focused = false))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,98 +20,99 @@ import com.jakewharton.mosaic.terminal.event.KeyboardEvent.Companion.Right
|
|||||||
import com.jakewharton.mosaic.terminal.event.KeyboardEvent.Companion.Up
|
import com.jakewharton.mosaic.terminal.event.KeyboardEvent.Companion.Up
|
||||||
import com.jakewharton.mosaic.terminal.event.UnknownEvent
|
import com.jakewharton.mosaic.terminal.event.UnknownEvent
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
||||||
class TerminalParserCsiKeyboardEventTest : BaseTerminalParserTest() {
|
class TerminalParserCsiKeyboardEventTest : BaseTerminalParserTest() {
|
||||||
@Test fun up() {
|
@Test fun up() = runTest {
|
||||||
writer.writeHex("1b5b41")
|
writer.writeHex("1b5b41")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(Up))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(Up))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun down() {
|
@Test fun down() = runTest {
|
||||||
writer.writeHex("1b5b42")
|
writer.writeHex("1b5b42")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(Down))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(Down))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun right() {
|
@Test fun right() = runTest {
|
||||||
writer.writeHex("1b5b43")
|
writer.writeHex("1b5b43")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(Right))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(Right))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun left() {
|
@Test fun left() = runTest {
|
||||||
writer.writeHex("1b5b44")
|
writer.writeHex("1b5b44")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(Left))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(Left))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun begin() {
|
@Test fun begin() = runTest {
|
||||||
writer.writeHex("1b5b45")
|
writer.writeHex("1b5b45")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(KpBegin))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(KpBegin))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun end() {
|
@Test fun end() = runTest {
|
||||||
writer.writeHex("1b5b46")
|
writer.writeHex("1b5b46")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(End))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(End))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun home() {
|
@Test fun home() = runTest {
|
||||||
writer.writeHex("1b5b48")
|
writer.writeHex("1b5b48")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(Home))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(Home))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun modifierShiftUp() {
|
@Test fun modifierShiftUp() = runTest {
|
||||||
writer.writeHex("1b5b313b3241")
|
writer.writeHex("1b5b313b3241")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(Up, modifiers = ModifierShift))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(Up, modifiers = ModifierShift))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun modifierAltUp() {
|
@Test fun modifierAltUp() = runTest {
|
||||||
writer.writeHex("1b5b313b3341")
|
writer.writeHex("1b5b313b3341")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(Up, modifiers = ModifierAlt))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(Up, modifiers = ModifierAlt))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun modifierCtrlUp() {
|
@Test fun modifierCtrlUp() = runTest {
|
||||||
writer.writeHex("1b5b313b3541")
|
writer.writeHex("1b5b313b3541")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(Up, modifiers = ModifierCtrl))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(Up, modifiers = ModifierCtrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun modifierSuperUp() {
|
@Test fun modifierSuperUp() = runTest {
|
||||||
writer.writeHex("1b5b313b3941")
|
writer.writeHex("1b5b313b3941")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(Up, modifiers = ModifierSuper))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(Up, modifiers = ModifierSuper))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun modifierHyperUp() {
|
@Test fun modifierHyperUp() = runTest {
|
||||||
writer.writeHex("1b5b313b313741")
|
writer.writeHex("1b5b313b313741")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(Up, modifiers = ModifierHyper))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(Up, modifiers = ModifierHyper))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun modifierMetaUp() {
|
@Test fun modifierMetaUp() = runTest {
|
||||||
writer.writeHex("1b5b313b333341")
|
writer.writeHex("1b5b313b333341")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(Up, modifiers = ModifierMeta))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(Up, modifiers = ModifierMeta))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun modifierCapsLockUp() {
|
@Test fun modifierCapsLockUp() = runTest {
|
||||||
writer.writeHex("1b5b313b363541")
|
writer.writeHex("1b5b313b363541")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(Up, modifiers = ModifierCapsLock))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(Up, modifiers = ModifierCapsLock))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun modifierNumLockUp() {
|
@Test fun modifierNumLockUp() = runTest {
|
||||||
writer.writeHex("1b5b313b31323941")
|
writer.writeHex("1b5b313b31323941")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(Up, modifiers = ModifierNumLock))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(Up, modifiers = ModifierNumLock))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun non1p0() {
|
@Test fun non1p0() = runTest {
|
||||||
writer.writeHex("1b5b323b3248")
|
writer.writeHex("1b5b323b3248")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5b323b3248".hexToByteArray()),
|
UnknownEvent("1b5b323b3248".hexToByteArray()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun emptyModifier() {
|
@Test fun emptyModifier() = runTest {
|
||||||
writer.writeHex("1b5b313b48")
|
writer.writeHex("1b5b313b48")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5b313b48".hexToByteArray()),
|
UnknownEvent("1b5b313b48".hexToByteArray()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun nonDigitModifier() {
|
@Test fun nonDigitModifier() = runTest {
|
||||||
writer.writeHex("1b5b313b2f48")
|
writer.writeHex("1b5b313b2f48")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5b313b2f48".hexToByteArray()),
|
UnknownEvent("1b5b313b2f48".hexToByteArray()),
|
||||||
|
|||||||
@ -5,37 +5,38 @@ import assertk.assertions.isEqualTo
|
|||||||
import com.jakewharton.mosaic.terminal.event.KeyboardEvent
|
import com.jakewharton.mosaic.terminal.event.KeyboardEvent
|
||||||
import com.jakewharton.mosaic.terminal.event.KeyboardEvent.Companion.ModifierShift
|
import com.jakewharton.mosaic.terminal.event.KeyboardEvent.Companion.ModifierShift
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
||||||
class TerminalParserCsiKittyKeyboardEventTest : BaseTerminalParserTest() {
|
class TerminalParserCsiKittyKeyboardEventTest : BaseTerminalParserTest() {
|
||||||
@Test fun h() {
|
@Test fun h() = runTest {
|
||||||
writer.writeHex("1b5b31303475")
|
writer.writeHex("1b5b31303475")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
KeyboardEvent(0x68),
|
KeyboardEvent(0x68),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun shiftH() {
|
@Test fun shiftH() = runTest {
|
||||||
writer.writeHex("1b5b3130343b3275")
|
writer.writeHex("1b5b3130343b3275")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
KeyboardEvent(0x68, modifiers = ModifierShift),
|
KeyboardEvent(0x68, modifiers = ModifierShift),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun shiftHWithAlternate() {
|
@Test fun shiftHWithAlternate() = runTest {
|
||||||
writer.writeHex("1b5b3130343a37323b3275")
|
writer.writeHex("1b5b3130343a37323b3275")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
KeyboardEvent(0x68, 0x48, modifiers = ModifierShift),
|
KeyboardEvent(0x68, 0x48, modifiers = ModifierShift),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun shiftHWithReleaseEventType() {
|
@Test fun shiftHWithReleaseEventType() = runTest {
|
||||||
writer.writeHex("1b5b3130343b323a3375")
|
writer.writeHex("1b5b3130343b323a3375")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
KeyboardEvent(0x68, modifiers = ModifierShift, eventType = 3),
|
KeyboardEvent(0x68, modifiers = ModifierShift, eventType = 3),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun hWithAssociatedText() {
|
@Test fun hWithAssociatedText() = runTest {
|
||||||
writer.writeHex("1b5b3130343b3b31303475")
|
writer.writeHex("1b5b3130343b3b31303475")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
KeyboardEvent(0x68, text = "h"),
|
KeyboardEvent(0x68, text = "h"),
|
||||||
|
|||||||
@ -5,31 +5,32 @@ import assertk.assertions.isEqualTo
|
|||||||
import com.jakewharton.mosaic.terminal.event.KittyKeyboardQueryEvent
|
import com.jakewharton.mosaic.terminal.event.KittyKeyboardQueryEvent
|
||||||
import com.jakewharton.mosaic.terminal.event.UnknownEvent
|
import com.jakewharton.mosaic.terminal.event.UnknownEvent
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
||||||
class TerminalParserCsiKittyKeyboardQueryEventTest : BaseTerminalParserTest() {
|
class TerminalParserCsiKittyKeyboardQueryEventTest : BaseTerminalParserTest() {
|
||||||
@Test fun flagsNone() {
|
@Test fun flagsNone() = runTest {
|
||||||
writer.writeHex("1b5b3f3075")
|
writer.writeHex("1b5b3f3075")
|
||||||
assertThat(parser.next()).isEqualTo(KittyKeyboardQueryEvent(0))
|
assertThat(parser.next()).isEqualTo(KittyKeyboardQueryEvent(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun flagsAll() {
|
@Test fun flagsAll() = runTest {
|
||||||
writer.writeHex("1b5b3f333175")
|
writer.writeHex("1b5b3f333175")
|
||||||
assertThat(parser.next()).isEqualTo(KittyKeyboardQueryEvent(31))
|
assertThat(parser.next()).isEqualTo(KittyKeyboardQueryEvent(31))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun flagsUnknown() {
|
@Test fun flagsUnknown() = runTest {
|
||||||
writer.writeHex("1b5b3f31323875")
|
writer.writeHex("1b5b3f31323875")
|
||||||
assertThat(parser.next()).isEqualTo(KittyKeyboardQueryEvent(128))
|
assertThat(parser.next()).isEqualTo(KittyKeyboardQueryEvent(128))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun flagsMissing() {
|
@Test fun flagsMissing() = runTest {
|
||||||
writer.writeHex("1b5b3f75")
|
writer.writeHex("1b5b3f75")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5b3f75".hexToByteArray()),
|
UnknownEvent("1b5b3f75".hexToByteArray()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun flagsNonDigit() {
|
@Test fun flagsNonDigit() = runTest {
|
||||||
writer.writeHex("1b5b3f312b2075")
|
writer.writeHex("1b5b3f312b2075")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5b3f312b2075".hexToByteArray()),
|
UnknownEvent("1b5b3f312b2075".hexToByteArray()),
|
||||||
|
|||||||
@ -7,114 +7,115 @@ import com.jakewharton.mosaic.terminal.event.MouseEvent.Button
|
|||||||
import com.jakewharton.mosaic.terminal.event.MouseEvent.Type
|
import com.jakewharton.mosaic.terminal.event.MouseEvent.Type
|
||||||
import com.jakewharton.mosaic.terminal.event.UnknownEvent
|
import com.jakewharton.mosaic.terminal.event.UnknownEvent
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
||||||
class TerminalParserCsiMouseEventTest : BaseTerminalParserTest() {
|
class TerminalParserCsiMouseEventTest : BaseTerminalParserTest() {
|
||||||
@Test fun motion() {
|
@Test fun motion() = runTest {
|
||||||
writer.writeHex("1b5b4d434837")
|
writer.writeHex("1b5b4d434837")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
MouseEvent(39, 22, Type.Motion, Button.None),
|
MouseEvent(39, 22, Type.Motion, Button.None),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun click() {
|
@Test fun click() = runTest {
|
||||||
writer.writeHex("1b5b4d204837")
|
writer.writeHex("1b5b4d204837")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
MouseEvent(39, 22, Type.Press, Button.Left),
|
MouseEvent(39, 22, Type.Press, Button.Left),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun drag() {
|
@Test fun drag() = runTest {
|
||||||
writer.writeHex("1b5b4d404837")
|
writer.writeHex("1b5b4d404837")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
MouseEvent(39, 22, Type.Drag, Button.Left),
|
MouseEvent(39, 22, Type.Drag, Button.Left),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun clickMouseUp() {
|
@Test fun clickMouseUp() = runTest {
|
||||||
writer.writeHex("1b5b4d234837")
|
writer.writeHex("1b5b4d234837")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
MouseEvent(39, 22, Type.Press, Button.None),
|
MouseEvent(39, 22, Type.Press, Button.None),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun shiftClick() {
|
@Test fun shiftClick() = runTest {
|
||||||
writer.writeHex("1b5b4d244837")
|
writer.writeHex("1b5b4d244837")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
MouseEvent(39, 22, Type.Press, Button.Left, shift = true),
|
MouseEvent(39, 22, Type.Press, Button.Left, shift = true),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun altClick() {
|
@Test fun altClick() = runTest {
|
||||||
writer.writeHex("1b5b4d284837")
|
writer.writeHex("1b5b4d284837")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
MouseEvent(39, 22, Type.Press, Button.Left, alt = true),
|
MouseEvent(39, 22, Type.Press, Button.Left, alt = true),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlClick() {
|
@Test fun ctrlClick() = runTest {
|
||||||
writer.writeHex("1b5b4d304837")
|
writer.writeHex("1b5b4d304837")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
MouseEvent(39, 22, Type.Press, Button.Left, ctrl = true),
|
MouseEvent(39, 22, Type.Press, Button.Left, ctrl = true),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun clickRight() {
|
@Test fun clickRight() = runTest {
|
||||||
writer.writeHex("1b5b4d224837")
|
writer.writeHex("1b5b4d224837")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
MouseEvent(39, 22, Type.Press, Button.Right),
|
MouseEvent(39, 22, Type.Press, Button.Right),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun clickMiddle() {
|
@Test fun clickMiddle() = runTest {
|
||||||
writer.writeHex("1b5b4d214837")
|
writer.writeHex("1b5b4d214837")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
MouseEvent(39, 22, Type.Press, Button.Middle),
|
MouseEvent(39, 22, Type.Press, Button.Middle),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun clickWheelUp() {
|
@Test fun clickWheelUp() = runTest {
|
||||||
writer.writeHex("1b5b4d604837")
|
writer.writeHex("1b5b4d604837")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
MouseEvent(39, 22, Type.Press, Button.WheelUp),
|
MouseEvent(39, 22, Type.Press, Button.WheelUp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun clickWheelDown() {
|
@Test fun clickWheelDown() = runTest {
|
||||||
writer.writeHex("1b5b4d614837")
|
writer.writeHex("1b5b4d614837")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
MouseEvent(39, 22, Type.Press, Button.WheelDown),
|
MouseEvent(39, 22, Type.Press, Button.WheelDown),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun clickButton8() {
|
@Test fun clickButton8() = runTest {
|
||||||
writer.writeHex("1b5b4da04837")
|
writer.writeHex("1b5b4da04837")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
MouseEvent(39, 22, Type.Press, Button.Button8),
|
MouseEvent(39, 22, Type.Press, Button.Button8),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun clickButton9() {
|
@Test fun clickButton9() = runTest {
|
||||||
writer.writeHex("1b5b4da14837")
|
writer.writeHex("1b5b4da14837")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
MouseEvent(39, 22, Type.Press, Button.Button9),
|
MouseEvent(39, 22, Type.Press, Button.Button9),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun clickButton10() {
|
@Test fun clickButton10() = runTest {
|
||||||
writer.writeHex("1b5b4da24837")
|
writer.writeHex("1b5b4da24837")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
MouseEvent(39, 22, Type.Press, Button.Button10),
|
MouseEvent(39, 22, Type.Press, Button.Button10),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun clickButton11() {
|
@Test fun clickButton11() = runTest {
|
||||||
writer.writeHex("1b5b4da34837")
|
writer.writeHex("1b5b4da34837")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
MouseEvent(39, 22, Type.Press, Button.Button11),
|
MouseEvent(39, 22, Type.Press, Button.Button11),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun clickUtf8() {
|
@Test fun clickUtf8() = runTest {
|
||||||
parser.xtermExtendedUtf8Mouse = true
|
parser.xtermExtendedUtf8Mouse = true
|
||||||
|
|
||||||
writer.writeHex("1b5b4d20c28037")
|
writer.writeHex("1b5b4d20c28037")
|
||||||
@ -125,7 +126,7 @@ class TerminalParserCsiMouseEventTest : BaseTerminalParserTest() {
|
|||||||
|
|
||||||
// TODO all types & buttons utf-8 in both single-byte and multi-byte form
|
// TODO all types & buttons utf-8 in both single-byte and multi-byte form
|
||||||
|
|
||||||
@Test fun lowercaseMDelimiterInvalid() {
|
@Test fun lowercaseMDelimiterInvalid() = runTest {
|
||||||
writer.writeHex("1b5b6d204837")
|
writer.writeHex("1b5b6d204837")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5b6d".hexToByteArray()),
|
UnknownEvent("1b5b6d".hexToByteArray()),
|
||||||
|
|||||||
@ -5,24 +5,25 @@ import assertk.assertions.isEqualTo
|
|||||||
import com.jakewharton.mosaic.terminal.event.OperatingStatusResponseEvent
|
import com.jakewharton.mosaic.terminal.event.OperatingStatusResponseEvent
|
||||||
import com.jakewharton.mosaic.terminal.event.UnknownEvent
|
import com.jakewharton.mosaic.terminal.event.UnknownEvent
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
||||||
class TerminalParserCsiOperatingStatusResponseEventTest : BaseTerminalParserTest() {
|
class TerminalParserCsiOperatingStatusResponseEventTest : BaseTerminalParserTest() {
|
||||||
@Test fun ok() {
|
@Test fun ok() = runTest {
|
||||||
writer.writeHex("1b5b306e")
|
writer.writeHex("1b5b306e")
|
||||||
assertThat(parser.next()).isEqualTo(OperatingStatusResponseEvent(ok = true))
|
assertThat(parser.next()).isEqualTo(OperatingStatusResponseEvent(ok = true))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun notOk() {
|
@Test fun notOk() = runTest {
|
||||||
writer.writeHex("1b5b336e")
|
writer.writeHex("1b5b336e")
|
||||||
assertThat(parser.next()).isEqualTo(OperatingStatusResponseEvent(ok = false))
|
assertThat(parser.next()).isEqualTo(OperatingStatusResponseEvent(ok = false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun unknownP1() {
|
@Test fun unknownP1() = runTest {
|
||||||
writer.writeHex("1b5b316e")
|
writer.writeHex("1b5b316e")
|
||||||
assertThat(parser.next()).isEqualTo(UnknownEvent("1b5b316e".hexToByteArray()))
|
assertThat(parser.next()).isEqualTo(UnknownEvent("1b5b316e".hexToByteArray()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun nonDigitP1() {
|
@Test fun nonDigitP1() = runTest {
|
||||||
writer.writeHex("1b5b2b6e")
|
writer.writeHex("1b5b2b6e")
|
||||||
assertThat(parser.next()).isEqualTo(UnknownEvent("1b5b2b6e".hexToByteArray()))
|
assertThat(parser.next()).isEqualTo(UnknownEvent("1b5b2b6e".hexToByteArray()))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,21 +5,22 @@ import assertk.assertions.isEqualTo
|
|||||||
import com.jakewharton.mosaic.terminal.event.PrimaryDeviceAttributesEvent
|
import com.jakewharton.mosaic.terminal.event.PrimaryDeviceAttributesEvent
|
||||||
import com.jakewharton.mosaic.terminal.event.UnknownEvent
|
import com.jakewharton.mosaic.terminal.event.UnknownEvent
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
||||||
class TerminalParserCsiPrimaryDeviceAttributesEventTest : BaseTerminalParserTest() {
|
class TerminalParserCsiPrimaryDeviceAttributesEventTest : BaseTerminalParserTest() {
|
||||||
@Test fun noLeadingQuestionMarkIsUnknown() {
|
@Test fun noLeadingQuestionMarkIsUnknown() = runTest {
|
||||||
writer.writeHex("1b5b303063")
|
writer.writeHex("1b5b303063")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5b303063".hexToByteArray()),
|
UnknownEvent("1b5b303063".hexToByteArray()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun emptyData() {
|
@Test fun emptyData() = runTest {
|
||||||
writer.writeHex("1b5b3f63")
|
writer.writeHex("1b5b3f63")
|
||||||
assertThat(parser.next()).isEqualTo(PrimaryDeviceAttributesEvent(data = ""))
|
assertThat(parser.next()).isEqualTo(PrimaryDeviceAttributesEvent(data = ""))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun data() {
|
@Test fun data() = runTest {
|
||||||
writer.writeHex("1b5b3f323b3263")
|
writer.writeHex("1b5b3f323b3263")
|
||||||
assertThat(parser.next()).isEqualTo(PrimaryDeviceAttributesEvent(data = "2;2"))
|
assertThat(parser.next()).isEqualTo(PrimaryDeviceAttributesEvent(data = "2;2"))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,36 +5,37 @@ import assertk.assertions.isEqualTo
|
|||||||
import com.jakewharton.mosaic.terminal.event.ResizeEvent
|
import com.jakewharton.mosaic.terminal.event.ResizeEvent
|
||||||
import com.jakewharton.mosaic.terminal.event.UnknownEvent
|
import com.jakewharton.mosaic.terminal.event.UnknownEvent
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
||||||
class TerminalParserCsiResizeEventTest : BaseTerminalParserTest() {
|
class TerminalParserCsiResizeEventTest : BaseTerminalParserTest() {
|
||||||
@Test fun basic() {
|
@Test fun basic() = runTest {
|
||||||
writer.writeHex("1b5b34383b313b323b333b3474")
|
writer.writeHex("1b5b34383b313b323b333b3474")
|
||||||
assertThat(parser.next()).isEqualTo(ResizeEvent(2, 1, 4, 3))
|
assertThat(parser.next()).isEqualTo(ResizeEvent(2, 1, 4, 3))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun pixelSizeAsZero() {
|
@Test fun pixelSizeAsZero() = runTest {
|
||||||
writer.writeHex("1b5b34383b313b323b303b3074")
|
writer.writeHex("1b5b34383b313b323b303b3074")
|
||||||
assertThat(parser.next()).isEqualTo(ResizeEvent(2, 1, 0, 0))
|
assertThat(parser.next()).isEqualTo(ResizeEvent(2, 1, 0, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun subparametersIgnored() {
|
@Test fun subparametersIgnored() = runTest {
|
||||||
writer.writeHex("1b5b34383b313a39393b323a39383a39373b333a39393a3a39373b343a39393a74")
|
writer.writeHex("1b5b34383b313a39393b323a39383a39373b333a39393a3a39373b343a39393a74")
|
||||||
assertThat(parser.next()).isEqualTo(ResizeEvent(2, 1, 4, 3))
|
assertThat(parser.next()).isEqualTo(ResizeEvent(2, 1, 4, 3))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun emptySubparametersIgnored() {
|
@Test fun emptySubparametersIgnored() = runTest {
|
||||||
writer.writeHex("1b5b34383b313a3b323a3b333a3b343a74")
|
writer.writeHex("1b5b34383b313a3b323a3b333a3b343a74")
|
||||||
assertThat(parser.next()).isEqualTo(ResizeEvent(2, 1, 4, 3))
|
assertThat(parser.next()).isEqualTo(ResizeEvent(2, 1, 4, 3))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun emptyModeFails() {
|
@Test fun emptyModeFails() = runTest {
|
||||||
writer.writeHex("1b5b3b313b323b333b3474")
|
writer.writeHex("1b5b3b313b323b333b3474")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5b3b313b323b333b3474".hexToByteArray()),
|
UnknownEvent("1b5b3b313b323b333b3474".hexToByteArray()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun emptyParameterFails() {
|
@Test fun emptyParameterFails() = runTest {
|
||||||
writer.writeHex("1b5b34383b3b323b333b3474")
|
writer.writeHex("1b5b34383b3b323b333b3474")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5b34383b3b323b333b3474".hexToByteArray()),
|
UnknownEvent("1b5b34383b3b323b333b3474".hexToByteArray()),
|
||||||
@ -53,7 +54,7 @@ class TerminalParserCsiResizeEventTest : BaseTerminalParserTest() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun nonDigitParameterFails() {
|
@Test fun nonDigitParameterFails() = runTest {
|
||||||
writer.writeHex("1b5b34383b312e303b323b333b3474")
|
writer.writeHex("1b5b34383b312e303b323b333b3474")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5b34383b312e303b323b333b3474".hexToByteArray()),
|
UnknownEvent("1b5b34383b312e303b323b333b3474".hexToByteArray()),
|
||||||
|
|||||||
@ -5,40 +5,41 @@ import assertk.assertions.isEqualTo
|
|||||||
import com.jakewharton.mosaic.terminal.event.SystemThemeEvent
|
import com.jakewharton.mosaic.terminal.event.SystemThemeEvent
|
||||||
import com.jakewharton.mosaic.terminal.event.UnknownEvent
|
import com.jakewharton.mosaic.terminal.event.UnknownEvent
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
||||||
class TerminalParserCsiSystemThemeEventTest : BaseTerminalParserTest() {
|
class TerminalParserCsiSystemThemeEventTest : BaseTerminalParserTest() {
|
||||||
@Test fun dark() {
|
@Test fun dark() = runTest {
|
||||||
writer.writeHex("1b5b3f3939373b316e")
|
writer.writeHex("1b5b3f3939373b316e")
|
||||||
assertThat(parser.next()).isEqualTo(SystemThemeEvent(isDark = true))
|
assertThat(parser.next()).isEqualTo(SystemThemeEvent(isDark = true))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun light() {
|
@Test fun light() = runTest {
|
||||||
writer.writeHex("1b5b3f3939373b326e")
|
writer.writeHex("1b5b3f3939373b326e")
|
||||||
assertThat(parser.next()).isEqualTo(SystemThemeEvent(isDark = false))
|
assertThat(parser.next()).isEqualTo(SystemThemeEvent(isDark = false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun missingP2() {
|
@Test fun missingP2() = runTest {
|
||||||
writer.writeHex("1b5b3f3939373b6e")
|
writer.writeHex("1b5b3f3939373b6e")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5b3f3939373b6e".hexToByteArray()),
|
UnknownEvent("1b5b3f3939373b6e".hexToByteArray()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun unknownP2() {
|
@Test fun unknownP2() = runTest {
|
||||||
writer.writeHex("1b5b3f3939373b346e")
|
writer.writeHex("1b5b3f3939373b346e")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5b3f3939373b346e".hexToByteArray()),
|
UnknownEvent("1b5b3f3939373b346e".hexToByteArray()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun nonDigitP2() {
|
@Test fun nonDigitP2() = runTest {
|
||||||
writer.writeHex("1b5b3f3939373b2b6e")
|
writer.writeHex("1b5b3f3939373b2b6e")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5b3f3939373b2b6e".hexToByteArray()),
|
UnknownEvent("1b5b3f3939373b2b6e".hexToByteArray()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun tooLongP2() {
|
@Test fun tooLongP2() = runTest {
|
||||||
writer.writeHex("1b5b3f3939373b31316e")
|
writer.writeHex("1b5b3f3939373b31316e")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5b3f3939373b31316e".hexToByteArray()),
|
UnknownEvent("1b5b3f3939373b31316e".hexToByteArray()),
|
||||||
|
|||||||
@ -5,14 +5,15 @@ import assertk.assertions.isEqualTo
|
|||||||
import com.jakewharton.mosaic.terminal.event.UnknownEvent
|
import com.jakewharton.mosaic.terminal.event.UnknownEvent
|
||||||
import com.jakewharton.mosaic.terminal.event.XtermCharacterSizeEvent
|
import com.jakewharton.mosaic.terminal.event.XtermCharacterSizeEvent
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
||||||
class TerminalParserCsiXtermCharacterSizeEventTest : BaseTerminalParserTest() {
|
class TerminalParserCsiXtermCharacterSizeEventTest : BaseTerminalParserTest() {
|
||||||
@Test fun basic() {
|
@Test fun basic() = runTest {
|
||||||
writer.writeHex("1b5b383b313b3274")
|
writer.writeHex("1b5b383b313b3274")
|
||||||
assertThat(parser.next()).isEqualTo(XtermCharacterSizeEvent(1, 2))
|
assertThat(parser.next()).isEqualTo(XtermCharacterSizeEvent(1, 2))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun emptyParameterFails() {
|
@Test fun emptyParameterFails() = runTest {
|
||||||
writer.writeHex("1b5b383b3b3274")
|
writer.writeHex("1b5b383b3b3274")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5b383b3b3274".hexToByteArray()),
|
UnknownEvent("1b5b383b3b3274".hexToByteArray()),
|
||||||
@ -23,7 +24,7 @@ class TerminalParserCsiXtermCharacterSizeEventTest : BaseTerminalParserTest() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun nonDigitParameterFails() {
|
@Test fun nonDigitParameterFails() = runTest {
|
||||||
writer.writeHex("1b5b383b223b3274")
|
writer.writeHex("1b5b383b223b3274")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5b383b223b3274".hexToByteArray()),
|
UnknownEvent("1b5b383b223b3274".hexToByteArray()),
|
||||||
|
|||||||
@ -5,14 +5,15 @@ import assertk.assertions.isEqualTo
|
|||||||
import com.jakewharton.mosaic.terminal.event.UnknownEvent
|
import com.jakewharton.mosaic.terminal.event.UnknownEvent
|
||||||
import com.jakewharton.mosaic.terminal.event.XtermPixelSizeEvent
|
import com.jakewharton.mosaic.terminal.event.XtermPixelSizeEvent
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
||||||
class TerminalParserCsiXtermPixelSizeEventTest : BaseTerminalParserTest() {
|
class TerminalParserCsiXtermPixelSizeEventTest : BaseTerminalParserTest() {
|
||||||
@Test fun basic() {
|
@Test fun basic() = runTest {
|
||||||
writer.writeHex("1b5b343b313b3274")
|
writer.writeHex("1b5b343b313b3274")
|
||||||
assertThat(parser.next()).isEqualTo(XtermPixelSizeEvent(1, 2))
|
assertThat(parser.next()).isEqualTo(XtermPixelSizeEvent(1, 2))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun emptyParameterFails() {
|
@Test fun emptyParameterFails() = runTest {
|
||||||
writer.writeHex("1b5b343b3b3274")
|
writer.writeHex("1b5b343b3b3274")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5b343b3b3274".hexToByteArray()),
|
UnknownEvent("1b5b343b3b3274".hexToByteArray()),
|
||||||
@ -23,7 +24,7 @@ class TerminalParserCsiXtermPixelSizeEventTest : BaseTerminalParserTest() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun nonDigitParameterFails() {
|
@Test fun nonDigitParameterFails() = runTest {
|
||||||
writer.writeHex("1b5b343b223b3274")
|
writer.writeHex("1b5b343b223b3274")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5b343b223b3274".hexToByteArray()),
|
UnknownEvent("1b5b343b223b3274".hexToByteArray()),
|
||||||
|
|||||||
@ -4,14 +4,15 @@ import assertk.assertThat
|
|||||||
import assertk.assertions.isEqualTo
|
import assertk.assertions.isEqualTo
|
||||||
import com.jakewharton.mosaic.terminal.event.TerminalVersionEvent
|
import com.jakewharton.mosaic.terminal.event.TerminalVersionEvent
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
||||||
class TerminalParserDcsTerminalVersionEventTest : BaseTerminalParserTest() {
|
class TerminalParserDcsTerminalVersionEventTest : BaseTerminalParserTest() {
|
||||||
@Test fun empty() {
|
@Test fun empty() = runTest {
|
||||||
writer.writeHex("1b503e7c1b5c")
|
writer.writeHex("1b503e7c1b5c")
|
||||||
assertThat(parser.next()).isEqualTo(TerminalVersionEvent(""))
|
assertThat(parser.next()).isEqualTo(TerminalVersionEvent(""))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun text() {
|
@Test fun text() = runTest {
|
||||||
writer.writeHex("1b503e7c68656c6c6f1b5c")
|
writer.writeHex("1b503e7c68656c6c6f1b5c")
|
||||||
assertThat(parser.next()).isEqualTo(TerminalVersionEvent("hello"))
|
assertThat(parser.next()).isEqualTo(TerminalVersionEvent("hello"))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,9 +5,10 @@ import assertk.assertions.isEqualTo
|
|||||||
import com.jakewharton.mosaic.terminal.event.KeyboardEvent
|
import com.jakewharton.mosaic.terminal.event.KeyboardEvent
|
||||||
import com.jakewharton.mosaic.terminal.event.KeyboardEvent.Companion.ModifierCtrl
|
import com.jakewharton.mosaic.terminal.event.KeyboardEvent.Companion.ModifierCtrl
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
||||||
class TerminalParserGroundKeyboardEventTest : BaseTerminalParserTest() {
|
class TerminalParserGroundKeyboardEventTest : BaseTerminalParserTest() {
|
||||||
@Test fun graphic() {
|
@Test fun graphic() = runTest {
|
||||||
for (codepoint in 0x20..0x7f) {
|
for (codepoint in 0x20..0x7f) {
|
||||||
val hex = codepoint.toString(16)
|
val hex = codepoint.toString(16)
|
||||||
writer.writeHex(hex)
|
writer.writeHex(hex)
|
||||||
@ -15,172 +16,172 @@ class TerminalParserGroundKeyboardEventTest : BaseTerminalParserTest() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlShiftAt() {
|
@Test fun ctrlShiftAt() = runTest {
|
||||||
writer.writeHex("00")
|
writer.writeHex("00")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent('@'.code, modifiers = ModifierCtrl))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent('@'.code, modifiers = ModifierCtrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlA() {
|
@Test fun ctrlA() = runTest {
|
||||||
writer.writeHex("01")
|
writer.writeHex("01")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent('a'.code, modifiers = ModifierCtrl))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent('a'.code, modifiers = ModifierCtrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlB() {
|
@Test fun ctrlB() = runTest {
|
||||||
writer.writeHex("02")
|
writer.writeHex("02")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent('b'.code, modifiers = ModifierCtrl))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent('b'.code, modifiers = ModifierCtrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlC() {
|
@Test fun ctrlC() = runTest {
|
||||||
writer.writeHex("03")
|
writer.writeHex("03")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent('c'.code, modifiers = ModifierCtrl))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent('c'.code, modifiers = ModifierCtrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlD() {
|
@Test fun ctrlD() = runTest {
|
||||||
writer.writeHex("04")
|
writer.writeHex("04")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent('d'.code, modifiers = ModifierCtrl))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent('d'.code, modifiers = ModifierCtrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlE() {
|
@Test fun ctrlE() = runTest {
|
||||||
writer.writeHex("05")
|
writer.writeHex("05")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent('e'.code, modifiers = ModifierCtrl))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent('e'.code, modifiers = ModifierCtrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlF() {
|
@Test fun ctrlF() = runTest {
|
||||||
writer.writeHex("06")
|
writer.writeHex("06")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent('f'.code, modifiers = ModifierCtrl))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent('f'.code, modifiers = ModifierCtrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlG() {
|
@Test fun ctrlG() = runTest {
|
||||||
writer.writeHex("07")
|
writer.writeHex("07")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent('g'.code, modifiers = ModifierCtrl))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent('g'.code, modifiers = ModifierCtrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlH() {
|
@Test fun ctrlH() = runTest {
|
||||||
writer.writeHex("08")
|
writer.writeHex("08")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(0x7f))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(0x7f))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlI() {
|
@Test fun ctrlI() = runTest {
|
||||||
writer.writeHex("09")
|
writer.writeHex("09")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(0x09))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(0x09))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlJ() {
|
@Test fun ctrlJ() = runTest {
|
||||||
writer.writeHex("0a")
|
writer.writeHex("0a")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(0x0d))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(0x0d))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlK() {
|
@Test fun ctrlK() = runTest {
|
||||||
writer.writeHex("0b")
|
writer.writeHex("0b")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent('k'.code, modifiers = ModifierCtrl))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent('k'.code, modifiers = ModifierCtrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlL() {
|
@Test fun ctrlL() = runTest {
|
||||||
writer.writeHex("0c")
|
writer.writeHex("0c")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent('l'.code, modifiers = ModifierCtrl))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent('l'.code, modifiers = ModifierCtrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlM() {
|
@Test fun ctrlM() = runTest {
|
||||||
writer.writeHex("0d")
|
writer.writeHex("0d")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(0x0d))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(0x0d))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlN() {
|
@Test fun ctrlN() = runTest {
|
||||||
writer.writeHex("0e")
|
writer.writeHex("0e")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent('n'.code, modifiers = ModifierCtrl))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent('n'.code, modifiers = ModifierCtrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlO() {
|
@Test fun ctrlO() = runTest {
|
||||||
writer.writeHex("0f")
|
writer.writeHex("0f")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent('o'.code, modifiers = ModifierCtrl))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent('o'.code, modifiers = ModifierCtrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlP() {
|
@Test fun ctrlP() = runTest {
|
||||||
writer.writeHex("10")
|
writer.writeHex("10")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent('p'.code, modifiers = ModifierCtrl))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent('p'.code, modifiers = ModifierCtrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlQ() {
|
@Test fun ctrlQ() = runTest {
|
||||||
writer.writeHex("11")
|
writer.writeHex("11")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent('q'.code, modifiers = ModifierCtrl))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent('q'.code, modifiers = ModifierCtrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlR() {
|
@Test fun ctrlR() = runTest {
|
||||||
writer.writeHex("12")
|
writer.writeHex("12")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent('r'.code, modifiers = ModifierCtrl))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent('r'.code, modifiers = ModifierCtrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlS() {
|
@Test fun ctrlS() = runTest {
|
||||||
writer.writeHex("13")
|
writer.writeHex("13")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent('s'.code, modifiers = ModifierCtrl))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent('s'.code, modifiers = ModifierCtrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlT() {
|
@Test fun ctrlT() = runTest {
|
||||||
writer.writeHex("14")
|
writer.writeHex("14")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent('t'.code, modifiers = ModifierCtrl))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent('t'.code, modifiers = ModifierCtrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlU() {
|
@Test fun ctrlU() = runTest {
|
||||||
writer.writeHex("15")
|
writer.writeHex("15")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent('u'.code, modifiers = ModifierCtrl))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent('u'.code, modifiers = ModifierCtrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlV() {
|
@Test fun ctrlV() = runTest {
|
||||||
writer.writeHex("16")
|
writer.writeHex("16")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent('v'.code, modifiers = ModifierCtrl))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent('v'.code, modifiers = ModifierCtrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlW() {
|
@Test fun ctrlW() = runTest {
|
||||||
writer.writeHex("17")
|
writer.writeHex("17")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent('w'.code, modifiers = ModifierCtrl))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent('w'.code, modifiers = ModifierCtrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlX() {
|
@Test fun ctrlX() = runTest {
|
||||||
writer.writeHex("18")
|
writer.writeHex("18")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent('x'.code, modifiers = ModifierCtrl))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent('x'.code, modifiers = ModifierCtrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlY() {
|
@Test fun ctrlY() = runTest {
|
||||||
writer.writeHex("19")
|
writer.writeHex("19")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent('y'.code, modifiers = ModifierCtrl))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent('y'.code, modifiers = ModifierCtrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun ctrlZ() {
|
@Test fun ctrlZ() = runTest {
|
||||||
writer.writeHex("1a")
|
writer.writeHex("1a")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent('z'.code, modifiers = ModifierCtrl))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent('z'.code, modifiers = ModifierCtrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun bareEscape() {
|
@Test fun bareEscape() = runTest {
|
||||||
writer.writeHex("1b")
|
writer.writeHex("1b")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(0x1b))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(0x1b))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun hex1c() {
|
@Test fun hex1c() = runTest {
|
||||||
writer.writeHex("1c")
|
writer.writeHex("1c")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(0x1c))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(0x1c))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun hex1d() {
|
@Test fun hex1d() = runTest {
|
||||||
writer.writeHex("1d")
|
writer.writeHex("1d")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(0x1d))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(0x1d))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun hex1e() {
|
@Test fun hex1e() = runTest {
|
||||||
writer.writeHex("1e")
|
writer.writeHex("1e")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(0x1e))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(0x1e))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun hex1f() {
|
@Test fun hex1f() = runTest {
|
||||||
writer.writeHex("1f")
|
writer.writeHex("1f")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(0x1f))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(0x1f))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun utf8TwoBytes() {
|
@Test fun utf8TwoBytes() = runTest {
|
||||||
writer.writeHex("cea9")
|
writer.writeHex("cea9")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent('Ω'.code))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent('Ω'.code))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun utf8ThreeBytes() {
|
@Test fun utf8ThreeBytes() = runTest {
|
||||||
writer.writeHex("e28988")
|
writer.writeHex("e28988")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent('≈'.code))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent('≈'.code))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,44 +6,45 @@ import com.jakewharton.mosaic.terminal.event.KittyPointerQueryNameEvent
|
|||||||
import com.jakewharton.mosaic.terminal.event.KittyPointerQuerySupportEvent
|
import com.jakewharton.mosaic.terminal.event.KittyPointerQuerySupportEvent
|
||||||
import com.jakewharton.mosaic.terminal.event.UnknownEvent
|
import com.jakewharton.mosaic.terminal.event.UnknownEvent
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
||||||
class TerminalParserOscKittyPointerQueryEventTest : BaseTerminalParserTest() {
|
class TerminalParserOscKittyPointerQueryEventTest : BaseTerminalParserTest() {
|
||||||
@Test fun emptyFails() {
|
@Test fun emptyFails() = runTest {
|
||||||
writer.writeHex("1b5d32323b1b5c")
|
writer.writeHex("1b5d32323b1b5c")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5d32323b1b5c".hexToByteArray()),
|
UnknownEvent("1b5d32323b1b5c".hexToByteArray()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun valuesSingleFalse() {
|
@Test fun valuesSingleFalse() = runTest {
|
||||||
writer.writeHex("1b5d32323b301b5c")
|
writer.writeHex("1b5d32323b301b5c")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
KittyPointerQuerySupportEvent(booleanArrayOf(false)),
|
KittyPointerQuerySupportEvent(booleanArrayOf(false)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun valuesSingleTrue() {
|
@Test fun valuesSingleTrue() = runTest {
|
||||||
writer.writeHex("1b5d32323b311b5c")
|
writer.writeHex("1b5d32323b311b5c")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
KittyPointerQuerySupportEvent(booleanArrayOf(true)),
|
KittyPointerQuerySupportEvent(booleanArrayOf(true)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun valuesSingleValueTrailingCommaFails() {
|
@Test fun valuesSingleValueTrailingCommaFails() = runTest {
|
||||||
writer.writeHex("1b5d32323b312c1b5c")
|
writer.writeHex("1b5d32323b312c1b5c")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5d32323b312c1b5c".hexToByteArray()),
|
UnknownEvent("1b5d32323b312c1b5c".hexToByteArray()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun valuesMultiple() {
|
@Test fun valuesMultiple() = runTest {
|
||||||
writer.writeHex("1b5d32323b302c302c312c312c301b5c")
|
writer.writeHex("1b5d32323b302c302c312c312c301b5c")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
KittyPointerQuerySupportEvent(booleanArrayOf(false, false, true, true, false)),
|
KittyPointerQuerySupportEvent(booleanArrayOf(false, false, true, true, false)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun valuesTons() {
|
@Test fun valuesTons() = runTest {
|
||||||
writer.writeHex("1b5d32323b302c302c312c312c302c302c312c312c302c302c312c312c302c302c312c312c302c302c312c312c302c302c312c312c302c302c312c312c301b5c")
|
writer.writeHex("1b5d32323b302c302c312c312c302c302c312c312c302c302c312c312c302c302c312c312c302c302c312c312c302c302c312c312c302c302c312c312c301b5c")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
KittyPointerQuerySupportEvent(
|
KittyPointerQuerySupportEvent(
|
||||||
@ -61,28 +62,28 @@ class TerminalParserOscKittyPointerQueryEventTest : BaseTerminalParserTest() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun nameSingleDigit() {
|
@Test fun nameSingleDigit() = runTest {
|
||||||
writer.writeHex("1b5d32323b321b5c")
|
writer.writeHex("1b5d32323b321b5c")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
KittyPointerQueryNameEvent("2"),
|
KittyPointerQueryNameEvent("2"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun nameLeadingValueDigit() {
|
@Test fun nameLeadingValueDigit() = runTest {
|
||||||
writer.writeHex("1b5d32323b30611b5c")
|
writer.writeHex("1b5d32323b30611b5c")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
KittyPointerQueryNameEvent("0a"),
|
KittyPointerQueryNameEvent("0a"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun nameValidRange() {
|
@Test fun nameValidRange() = runTest {
|
||||||
writer.writeHex("1b5d32323b6162636465666768696a6b6c6d6e6f707172737475767778797a303132333435363738392d5f1b5c")
|
writer.writeHex("1b5d32323b6162636465666768696a6b6c6d6e6f707172737475767778797a303132333435363738392d5f1b5c")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
KittyPointerQueryNameEvent("abcdefghijklmnopqrstuvwxyz0123456789-_"),
|
KittyPointerQueryNameEvent("abcdefghijklmnopqrstuvwxyz0123456789-_"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun nameInvalidRange() {
|
@Test fun nameInvalidRange() = runTest {
|
||||||
writer.writeHex("1b5d32323b6162633132334142431b5c")
|
writer.writeHex("1b5d32323b6162633132334142431b5c")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b5d32323b6162633132334142431b5c".hexToByteArray()),
|
UnknownEvent("1b5d32323b6162633132334142431b5c".hexToByteArray()),
|
||||||
|
|||||||
@ -16,66 +16,67 @@ import com.jakewharton.mosaic.terminal.event.KeyboardEvent.Companion.Up
|
|||||||
import com.jakewharton.mosaic.terminal.event.OperatingStatusResponseEvent
|
import com.jakewharton.mosaic.terminal.event.OperatingStatusResponseEvent
|
||||||
import com.jakewharton.mosaic.terminal.event.UnknownEvent
|
import com.jakewharton.mosaic.terminal.event.UnknownEvent
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
||||||
class TerminalParserSs3KeyboardEventTest : BaseTerminalParserTest() {
|
class TerminalParserSs3KeyboardEventTest : BaseTerminalParserTest() {
|
||||||
@Test fun up() {
|
@Test fun up() = runTest {
|
||||||
writer.writeHex("1b4f41")
|
writer.writeHex("1b4f41")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(Up))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(Up))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun down() {
|
@Test fun down() = runTest {
|
||||||
writer.writeHex("1b4f42")
|
writer.writeHex("1b4f42")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(Down))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(Down))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun right() {
|
@Test fun right() = runTest {
|
||||||
writer.writeHex("1b4f43")
|
writer.writeHex("1b4f43")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(Right))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(Right))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun left() {
|
@Test fun left() = runTest {
|
||||||
writer.writeHex("1b4f44")
|
writer.writeHex("1b4f44")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(Left))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(Left))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun end() {
|
@Test fun end() = runTest {
|
||||||
writer.writeHex("1b4f46")
|
writer.writeHex("1b4f46")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(End))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(End))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun home() {
|
@Test fun home() = runTest {
|
||||||
writer.writeHex("1b4f48")
|
writer.writeHex("1b4f48")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(Home))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(Home))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun f1() {
|
@Test fun f1() = runTest {
|
||||||
writer.writeHex("1b4f50")
|
writer.writeHex("1b4f50")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(F1))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(F1))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun f2() {
|
@Test fun f2() = runTest {
|
||||||
writer.writeHex("1b4f51")
|
writer.writeHex("1b4f51")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(F2))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(F2))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun f3() {
|
@Test fun f3() = runTest {
|
||||||
writer.writeHex("1b4f52")
|
writer.writeHex("1b4f52")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(F3))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(F3))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun f4() {
|
@Test fun f4() = runTest {
|
||||||
writer.writeHex("1b4f53")
|
writer.writeHex("1b4f53")
|
||||||
assertThat(parser.next()).isEqualTo(KeyboardEvent(F4))
|
assertThat(parser.next()).isEqualTo(KeyboardEvent(F4))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun invalidKey() {
|
@Test fun invalidKey() = runTest {
|
||||||
writer.writeHex("1b4f75")
|
writer.writeHex("1b4f75")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b4f75".hexToByteArray()),
|
UnknownEvent("1b4f75".hexToByteArray()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun keyIsEscapeDoesNotConsumeEscape() {
|
@Test fun keyIsEscapeDoesNotConsumeEscape() = runTest {
|
||||||
writer.writeHex("1b4f1b5b306e")
|
writer.writeHex("1b4f1b5b306e")
|
||||||
assertThat(parser.next()).isEqualTo(
|
assertThat(parser.next()).isEqualTo(
|
||||||
UnknownEvent("1b4f".hexToByteArray()),
|
UnknownEvent("1b4f".hexToByteArray()),
|
||||||
|
|||||||
@ -45,9 +45,135 @@ Java_com_jakewharton_mosaic_terminal_Jni_exitRawMode(JNIEnv *env, jclass type, j
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef struct jniPlatformEventHandler {
|
||||||
|
JNIEnv *env;
|
||||||
|
jobject instance;
|
||||||
|
jclass clazz;
|
||||||
|
jmethodID onFocus;
|
||||||
|
jmethodID onKey;
|
||||||
|
jmethodID onMouse;
|
||||||
|
jmethodID onResize;
|
||||||
|
} jniPlatformEventHandler;
|
||||||
|
|
||||||
|
void invokeOnFocusHandler(void *opaque, bool focused) {
|
||||||
|
jniPlatformEventHandler *handler = (jniPlatformEventHandler *) opaque;
|
||||||
|
(*handler->env)->CallNonvirtualVoidMethod(
|
||||||
|
handler->env,
|
||||||
|
handler->instance,
|
||||||
|
handler->clazz,
|
||||||
|
handler->onFocus,
|
||||||
|
focused
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void invokeOnKeyHandler(void *opaque) {
|
||||||
|
jniPlatformEventHandler *handler = (jniPlatformEventHandler *) opaque;
|
||||||
|
(*handler->env)->CallNonvirtualVoidMethod(
|
||||||
|
handler->env,
|
||||||
|
handler->instance,
|
||||||
|
handler->clazz,
|
||||||
|
handler->onKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void invokeOnMouseHandler(void *opaque) {
|
||||||
|
jniPlatformEventHandler *handler = (jniPlatformEventHandler *) opaque;
|
||||||
|
(*handler->env)->CallNonvirtualVoidMethod(
|
||||||
|
handler->env,
|
||||||
|
handler->instance,
|
||||||
|
handler->clazz,
|
||||||
|
handler->onMouse
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void invokeOnResizeHandler(void *opaque, int columns, int rows, int width, int height) {
|
||||||
|
jniPlatformEventHandler *handler = (jniPlatformEventHandler *) opaque;
|
||||||
|
(*handler->env)->CallNonvirtualVoidMethod(
|
||||||
|
handler->env,
|
||||||
|
handler->instance,
|
||||||
|
handler->clazz,
|
||||||
|
handler->onResize,
|
||||||
|
columns,
|
||||||
|
rows,
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
JNIEXPORT jlong JNICALL
|
JNIEXPORT jlong JNICALL
|
||||||
Java_com_jakewharton_mosaic_terminal_Jni_stdinReaderInit(JNIEnv *env, jclass type) {
|
Java_com_jakewharton_mosaic_terminal_Jni_platformEventHandlerInit(
|
||||||
stdinReaderResult result = stdinReader_init();
|
JNIEnv *env,
|
||||||
|
jclass type,
|
||||||
|
jobject instance
|
||||||
|
) {
|
||||||
|
jobject globalInstance = (*env)->NewGlobalRef(env, instance);
|
||||||
|
if (unlikely(globalInstance == NULL)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
jclass clazz = (*env)->FindClass(env, "com/jakewharton/mosaic/terminal/PlatformEventHandler");
|
||||||
|
if (unlikely(clazz == NULL)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
jmethodID onFocus = (*env)->GetMethodID(env, clazz, "onFocus", "(Z)V");
|
||||||
|
if (unlikely(onFocus == NULL)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
jmethodID onKey = (*env)->GetMethodID(env, clazz, "onKey", "()V");
|
||||||
|
if (unlikely(onKey == NULL)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
jmethodID onMouse = (*env)->GetMethodID(env, clazz, "onMouse", "()V");
|
||||||
|
if (unlikely(onMouse == NULL)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
jmethodID onResize = (*env)->GetMethodID(env, clazz, "onResize", "(IIII)V");
|
||||||
|
if (unlikely(onResize == NULL)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
jniPlatformEventHandler *jniHandler = malloc(sizeof(jniPlatformEventHandler));
|
||||||
|
if (unlikely(!jniHandler)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
jniHandler->env = env;
|
||||||
|
jniHandler->instance = globalInstance;
|
||||||
|
jniHandler->clazz = clazz;
|
||||||
|
jniHandler->onFocus = onFocus;
|
||||||
|
jniHandler->onKey = onKey;
|
||||||
|
jniHandler->onMouse = onMouse;
|
||||||
|
jniHandler->onResize = onResize;
|
||||||
|
|
||||||
|
platformEventHandler *handler = malloc(sizeof(platformEventHandler));
|
||||||
|
if (unlikely(!handler)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
handler->opaque = jniHandler;
|
||||||
|
handler->onFocus = invokeOnFocusHandler;
|
||||||
|
handler->onKey = invokeOnKeyHandler;
|
||||||
|
handler->onMouse = invokeOnMouseHandler;
|
||||||
|
handler->onResize = invokeOnResizeHandler;
|
||||||
|
|
||||||
|
return (jlong) handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_com_jakewharton_mosaic_terminal_Jni_platformEventHandlerFree(
|
||||||
|
JNIEnv *env,
|
||||||
|
jclass type,
|
||||||
|
jlong handlerOpaque
|
||||||
|
) {
|
||||||
|
platformEventHandler *handler = (platformEventHandler *) handlerOpaque;
|
||||||
|
jniPlatformEventHandler *jniHandler = handler->opaque;
|
||||||
|
jobject instance = jniHandler->instance;
|
||||||
|
free(handler);
|
||||||
|
free(jniHandler);
|
||||||
|
(*env)->DeleteGlobalRef(env, instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jlong JNICALL
|
||||||
|
Java_com_jakewharton_mosaic_terminal_Jni_stdinReaderInit(JNIEnv *env, jclass type, jlong handlerOpaque) {
|
||||||
|
platformEventHandler *handler = (platformEventHandler *) handlerOpaque;
|
||||||
|
stdinReaderResult result = stdinReader_init(handler);
|
||||||
if (likely(!result.error)) {
|
if (likely(!result.error)) {
|
||||||
return (jlong) result.reader;
|
return (jlong) result.reader;
|
||||||
}
|
}
|
||||||
@ -62,7 +188,7 @@ JNIEXPORT jint JNICALL
|
|||||||
Java_com_jakewharton_mosaic_terminal_Jni_stdinReaderRead(
|
Java_com_jakewharton_mosaic_terminal_Jni_stdinReaderRead(
|
||||||
JNIEnv *env,
|
JNIEnv *env,
|
||||||
jclass type,
|
jclass type,
|
||||||
jlong ptr,
|
jlong readerOpaque,
|
||||||
jbyteArray buffer,
|
jbyteArray buffer,
|
||||||
jint offset,
|
jint offset,
|
||||||
jint count
|
jint count
|
||||||
@ -70,7 +196,8 @@ Java_com_jakewharton_mosaic_terminal_Jni_stdinReaderRead(
|
|||||||
jbyte *nativeBuffer = (*env)->GetByteArrayElements(env, buffer, NULL);
|
jbyte *nativeBuffer = (*env)->GetByteArrayElements(env, buffer, NULL);
|
||||||
jbyte *nativeBufferAtOffset = nativeBuffer + offset;
|
jbyte *nativeBufferAtOffset = nativeBuffer + offset;
|
||||||
|
|
||||||
stdinRead read = stdinReader_read((stdinReader *) ptr, nativeBufferAtOffset, count);
|
stdinReader *reader = (stdinReader *) readerOpaque;
|
||||||
|
stdinRead read = stdinReader_read(reader, nativeBufferAtOffset, count);
|
||||||
|
|
||||||
(*env)->ReleaseByteArrayElements(env, buffer, nativeBuffer, 0);
|
(*env)->ReleaseByteArrayElements(env, buffer, nativeBuffer, 0);
|
||||||
|
|
||||||
@ -88,7 +215,7 @@ JNIEXPORT jint JNICALL
|
|||||||
Java_com_jakewharton_mosaic_terminal_Jni_stdinReaderReadWithTimeout(
|
Java_com_jakewharton_mosaic_terminal_Jni_stdinReaderReadWithTimeout(
|
||||||
JNIEnv *env,
|
JNIEnv *env,
|
||||||
jclass type,
|
jclass type,
|
||||||
jlong ptr,
|
jlong readerOpaque,
|
||||||
jbyteArray buffer,
|
jbyteArray buffer,
|
||||||
jint offset,
|
jint offset,
|
||||||
jint count,
|
jint count,
|
||||||
@ -97,8 +224,9 @@ Java_com_jakewharton_mosaic_terminal_Jni_stdinReaderReadWithTimeout(
|
|||||||
jbyte *nativeBuffer = (*env)->GetByteArrayElements(env, buffer, NULL);
|
jbyte *nativeBuffer = (*env)->GetByteArrayElements(env, buffer, NULL);
|
||||||
jbyte *nativeBufferAtOffset = nativeBuffer + offset;
|
jbyte *nativeBufferAtOffset = nativeBuffer + offset;
|
||||||
|
|
||||||
|
stdinReader *reader = (stdinReader *) readerOpaque;
|
||||||
stdinRead read = stdinReader_readWithTimeout(
|
stdinRead read = stdinReader_readWithTimeout(
|
||||||
(stdinReader *) ptr,
|
reader,
|
||||||
nativeBufferAtOffset,
|
nativeBufferAtOffset,
|
||||||
count,
|
count,
|
||||||
timeoutMillis
|
timeoutMillis
|
||||||
@ -117,24 +245,27 @@ Java_com_jakewharton_mosaic_terminal_Jni_stdinReaderReadWithTimeout(
|
|||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_com_jakewharton_mosaic_terminal_Jni_stdinReaderInterrupt(JNIEnv *env, jclass type, jlong ptr) {
|
Java_com_jakewharton_mosaic_terminal_Jni_stdinReaderInterrupt(JNIEnv *env, jclass type, jlong readerOpaque) {
|
||||||
platformError error = stdinReader_interrupt((stdinReader *) ptr);
|
stdinReader *reader = (stdinReader *) readerOpaque;
|
||||||
|
platformError error = stdinReader_interrupt(reader);
|
||||||
if (unlikely(error)) {
|
if (unlikely(error)) {
|
||||||
throwIse(env, error, "Unable to interrupt stdin reader");
|
throwIse(env, error, "Unable to interrupt stdin reader");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_com_jakewharton_mosaic_terminal_Jni_stdinReaderFree(JNIEnv *env, jclass type, jlong ptr) {
|
Java_com_jakewharton_mosaic_terminal_Jni_stdinReaderFree(JNIEnv *env, jclass type, jlong readerOpaque) {
|
||||||
platformError error = stdinReader_free((stdinReader *) ptr);
|
stdinReader *reader = (stdinReader *) readerOpaque;
|
||||||
|
platformError error = stdinReader_free(reader);
|
||||||
if (unlikely(error)) {
|
if (unlikely(error)) {
|
||||||
throwIse(env, error, "Unable to free stdin reader");
|
throwIse(env, error, "Unable to free stdin reader");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jlong JNICALL
|
JNIEXPORT jlong JNICALL
|
||||||
Java_com_jakewharton_mosaic_terminal_Jni_stdinWriterInit(JNIEnv *env, jclass type) {
|
Java_com_jakewharton_mosaic_terminal_Jni_stdinWriterInit(JNIEnv *env, jclass type, jlong handlerOpaque) {
|
||||||
stdinWriterResult result = stdinWriter_init();
|
platformEventHandler *handler = (platformEventHandler *) handlerOpaque;
|
||||||
|
stdinWriterResult result = stdinWriter_init(handler);
|
||||||
if (likely(!result.error)) {
|
if (likely(!result.error)) {
|
||||||
return (jlong) result.writer;
|
return (jlong) result.writer;
|
||||||
}
|
}
|
||||||
@ -149,13 +280,14 @@ JNIEXPORT void JNICALL
|
|||||||
Java_com_jakewharton_mosaic_terminal_Jni_stdinWriterWrite(
|
Java_com_jakewharton_mosaic_terminal_Jni_stdinWriterWrite(
|
||||||
JNIEnv *env,
|
JNIEnv *env,
|
||||||
jclass type,
|
jclass type,
|
||||||
jlong ptr,
|
jlong writerOpaque,
|
||||||
jbyteArray buffer
|
jbyteArray buffer
|
||||||
) {
|
) {
|
||||||
jsize count = (*env)->GetArrayLength(env, buffer);
|
jsize count = (*env)->GetArrayLength(env, buffer);
|
||||||
jbyte *nativeBuffer = (*env)->GetByteArrayElements(env, buffer, NULL);
|
jbyte *nativeBuffer = (*env)->GetByteArrayElements(env, buffer, NULL);
|
||||||
|
|
||||||
platformError error = stdinWriter_write((stdinWriter *) ptr, nativeBuffer, count);
|
stdinWriter *writer = (stdinWriter *) writerOpaque;
|
||||||
|
platformError error = stdinWriter_write(writer, nativeBuffer, count);
|
||||||
|
|
||||||
(*env)->ReleaseByteArrayElements(env, buffer, nativeBuffer, 0);
|
(*env)->ReleaseByteArrayElements(env, buffer, nativeBuffer, 0);
|
||||||
|
|
||||||
@ -165,6 +297,51 @@ Java_com_jakewharton_mosaic_terminal_Jni_stdinWriterWrite(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_com_jakewharton_mosaic_terminal_Jni_stdinWriterFocusEvent(
|
||||||
|
JNIEnv *env,
|
||||||
|
jclass type,
|
||||||
|
jlong writerOpaque,
|
||||||
|
bool focused
|
||||||
|
) {
|
||||||
|
stdinWriter *writer = (stdinWriter *) writerOpaque;
|
||||||
|
stdinWriter_focusEvent(writer, focused);
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_com_jakewharton_mosaic_terminal_Jni_stdinWriterKeyEvent(
|
||||||
|
JNIEnv *env,
|
||||||
|
jclass type,
|
||||||
|
jlong writerOpaque
|
||||||
|
) {
|
||||||
|
stdinWriter *writer = (stdinWriter *) writerOpaque;
|
||||||
|
stdinWriter_keyEvent(writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_com_jakewharton_mosaic_terminal_Jni_stdinWriterMouseEvent(
|
||||||
|
JNIEnv *env,
|
||||||
|
jclass type,
|
||||||
|
jlong writerOpaque
|
||||||
|
) {
|
||||||
|
stdinWriter *writer = (stdinWriter *) writerOpaque;
|
||||||
|
stdinWriter_mouseEvent(writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_com_jakewharton_mosaic_terminal_Jni_stdinWriterResizeEvent(
|
||||||
|
JNIEnv *env,
|
||||||
|
jclass type,
|
||||||
|
jlong writerOpaque,
|
||||||
|
jint columns,
|
||||||
|
jint rows,
|
||||||
|
jint width,
|
||||||
|
jint height
|
||||||
|
) {
|
||||||
|
stdinWriter *writer = (stdinWriter *) writerOpaque;
|
||||||
|
stdinWriter_resizeEvent(writer, columns, rows, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
JNIEXPORT jlong JNICALL
|
JNIEXPORT jlong JNICALL
|
||||||
Java_com_jakewharton_mosaic_terminal_Jni_stdinWriterGetReader(JNIEnv *env, jclass type, jlong ptr) {
|
Java_com_jakewharton_mosaic_terminal_Jni_stdinWriterGetReader(JNIEnv *env, jclass type, jlong ptr) {
|
||||||
return (jlong) stdinWriter_getReader((stdinWriter *) ptr);
|
return (jlong) stdinWriter_getReader((stdinWriter *) ptr);
|
||||||
|
|||||||
@ -19,7 +19,13 @@ internal object Jni {
|
|||||||
external fun exitRawMode(savedConfig: Long)
|
external fun exitRawMode(savedConfig: Long)
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
external fun stdinReaderInit(): Long
|
external fun platformEventHandlerInit(handler: PlatformEventHandler): Long
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
external fun platformEventHandlerFree(handler: Long)
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
external fun stdinReaderInit(handler: Long): Long
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
external fun stdinReaderRead(
|
external fun stdinReaderRead(
|
||||||
@ -45,7 +51,7 @@ internal object Jni {
|
|||||||
external fun stdinReaderFree(reader: Long)
|
external fun stdinReaderFree(reader: Long)
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
external fun stdinWriterInit(): Long
|
external fun stdinWriterInit(handler: Long): Long
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
external fun stdinWriterGetReader(writer: Long): Long
|
external fun stdinWriterGetReader(writer: Long): Long
|
||||||
@ -53,6 +59,24 @@ internal object Jni {
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
external fun stdinWriterWrite(writer: Long, buffer: ByteArray)
|
external fun stdinWriterWrite(writer: Long, buffer: ByteArray)
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
external fun stdinWriterFocusEvent(writer: Long, focused: Boolean)
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
external fun stdinWriterKeyEvent(writer: Long)
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
external fun stdinWriterMouseEvent(writer: Long)
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
external fun stdinWriterResizeEvent(
|
||||||
|
writer: Long,
|
||||||
|
columns: Int,
|
||||||
|
rows: Int,
|
||||||
|
width: Int,
|
||||||
|
height: Int,
|
||||||
|
)
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
external fun stdinWriterFree(writer: Long)
|
external fun stdinWriterFree(writer: Long)
|
||||||
|
|
||||||
|
|||||||
@ -2,15 +2,24 @@ package com.jakewharton.mosaic.terminal
|
|||||||
|
|
||||||
import com.jakewharton.mosaic.terminal.Jni.enterRawMode
|
import com.jakewharton.mosaic.terminal.Jni.enterRawMode
|
||||||
import com.jakewharton.mosaic.terminal.Jni.exitRawMode
|
import com.jakewharton.mosaic.terminal.Jni.exitRawMode
|
||||||
|
import com.jakewharton.mosaic.terminal.Jni.platformEventHandlerFree
|
||||||
|
import com.jakewharton.mosaic.terminal.Jni.platformEventHandlerInit
|
||||||
import com.jakewharton.mosaic.terminal.Jni.stdinReaderFree
|
import com.jakewharton.mosaic.terminal.Jni.stdinReaderFree
|
||||||
import com.jakewharton.mosaic.terminal.Jni.stdinReaderInit
|
import com.jakewharton.mosaic.terminal.Jni.stdinReaderInit
|
||||||
import com.jakewharton.mosaic.terminal.Jni.stdinReaderInterrupt
|
import com.jakewharton.mosaic.terminal.Jni.stdinReaderInterrupt
|
||||||
import com.jakewharton.mosaic.terminal.Jni.stdinReaderRead
|
import com.jakewharton.mosaic.terminal.Jni.stdinReaderRead
|
||||||
import com.jakewharton.mosaic.terminal.Jni.stdinReaderReadWithTimeout
|
import com.jakewharton.mosaic.terminal.Jni.stdinReaderReadWithTimeout
|
||||||
|
import com.jakewharton.mosaic.terminal.Jni.stdinWriterFocusEvent
|
||||||
import com.jakewharton.mosaic.terminal.Jni.stdinWriterFree
|
import com.jakewharton.mosaic.terminal.Jni.stdinWriterFree
|
||||||
import com.jakewharton.mosaic.terminal.Jni.stdinWriterGetReader
|
import com.jakewharton.mosaic.terminal.Jni.stdinWriterGetReader
|
||||||
import com.jakewharton.mosaic.terminal.Jni.stdinWriterInit
|
import com.jakewharton.mosaic.terminal.Jni.stdinWriterInit
|
||||||
|
import com.jakewharton.mosaic.terminal.Jni.stdinWriterKeyEvent
|
||||||
|
import com.jakewharton.mosaic.terminal.Jni.stdinWriterMouseEvent
|
||||||
|
import com.jakewharton.mosaic.terminal.Jni.stdinWriterResizeEvent
|
||||||
import com.jakewharton.mosaic.terminal.Jni.stdinWriterWrite
|
import com.jakewharton.mosaic.terminal.Jni.stdinWriterWrite
|
||||||
|
import com.jakewharton.mosaic.terminal.event.Event
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
|
||||||
|
|
||||||
public actual object Tty {
|
public actual object Tty {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@ -29,53 +38,94 @@ public actual object Tty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public actual fun stdinReader(): StdinReader {
|
public actual fun terminalReader(emitDebugEvents: Boolean): TerminalReader {
|
||||||
val reader = stdinReaderInit()
|
val events = Channel<Event>(UNLIMITED)
|
||||||
if (reader == 0L) throw OutOfMemoryError()
|
val handlerPtr = platformEventHandlerInit(PlatformEventHandler(events))
|
||||||
return StdinReader(reader)
|
if (handlerPtr != 0L) {
|
||||||
|
val readerPtr = stdinReaderInit(handlerPtr)
|
||||||
|
if (readerPtr != 0L) {
|
||||||
|
val platformInput = PlatformInput(readerPtr, handlerPtr)
|
||||||
|
return TerminalReader(platformInput, events, emitDebugEvents)
|
||||||
|
}
|
||||||
|
platformEventHandlerFree(handlerPtr)
|
||||||
|
}
|
||||||
|
throw OutOfMemoryError()
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmSynthetic // Hide from Java callers.
|
@JvmSynthetic // Hide from Java callers.
|
||||||
internal actual fun stdinWriter(): StdinWriter {
|
internal actual fun stdinWriter(emitDebugEvents: Boolean): StdinWriter {
|
||||||
val writer = stdinWriterInit()
|
val events = Channel<Event>(UNLIMITED)
|
||||||
if (writer == 0L) throw OutOfMemoryError()
|
val handlerPtr = platformEventHandlerInit(PlatformEventHandler(events))
|
||||||
val reader = stdinWriterGetReader(writer)
|
if (handlerPtr != 0L) {
|
||||||
return StdinWriter(writer, reader)
|
val writerPtr = stdinWriterInit(handlerPtr)
|
||||||
|
if (writerPtr != 0L) {
|
||||||
|
val readerPtr = stdinWriterGetReader(writerPtr)
|
||||||
|
val platformInput = PlatformInput(readerPtr, handlerPtr)
|
||||||
|
val terminalReader = TerminalReader(platformInput, events, emitDebugEvents)
|
||||||
|
return StdinWriter(writerPtr, terminalReader)
|
||||||
|
}
|
||||||
|
platformEventHandlerFree(handlerPtr)
|
||||||
|
}
|
||||||
|
throw OutOfMemoryError()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public actual class StdinReader internal constructor(
|
// TODO @JvmSynthetic https://youtrack.jetbrains.com/issue/KT-24981
|
||||||
private val readerPtr: Long,
|
internal actual class PlatformInput internal constructor(
|
||||||
|
private var readerPtr: Long,
|
||||||
|
private val handlerPtr: Long,
|
||||||
) : AutoCloseable {
|
) : AutoCloseable {
|
||||||
public actual fun read(buffer: ByteArray, offset: Int, count: Int): Int {
|
actual fun read(buffer: ByteArray, offset: Int, count: Int): Int {
|
||||||
return stdinReaderRead(readerPtr, buffer, offset, count)
|
return stdinReaderRead(readerPtr, buffer, offset, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
public actual fun readWithTimeout(buffer: ByteArray, offset: Int, count: Int, timeoutMillis: Int): Int {
|
actual fun readWithTimeout(buffer: ByteArray, offset: Int, count: Int, timeoutMillis: Int): Int {
|
||||||
return stdinReaderReadWithTimeout(readerPtr, buffer, offset, count, timeoutMillis)
|
return stdinReaderReadWithTimeout(readerPtr, buffer, offset, count, timeoutMillis)
|
||||||
}
|
}
|
||||||
|
|
||||||
public actual fun interrupt() {
|
actual fun interrupt() {
|
||||||
stdinReaderInterrupt(readerPtr)
|
stdinReaderInterrupt(readerPtr)
|
||||||
}
|
}
|
||||||
|
|
||||||
public actual override fun close() {
|
actual override fun close() {
|
||||||
stdinReaderFree(readerPtr)
|
if (readerPtr != 0L) {
|
||||||
|
stdinReaderFree(readerPtr)
|
||||||
|
readerPtr = 0
|
||||||
|
platformEventHandlerFree(handlerPtr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO @JvmSynthetic https://youtrack.jetbrains.com/issue/KT-24981
|
// TODO @JvmSynthetic https://youtrack.jetbrains.com/issue/KT-24981
|
||||||
internal actual class StdinWriter internal constructor(
|
internal actual class StdinWriter internal constructor(
|
||||||
private val writerPtr: Long,
|
private var writerPtr: Long,
|
||||||
readerPtr: Long,
|
actual val reader: TerminalReader,
|
||||||
) : AutoCloseable {
|
) : AutoCloseable {
|
||||||
actual val reader: StdinReader = StdinReader(readerPtr)
|
|
||||||
|
|
||||||
actual fun write(buffer: ByteArray) {
|
actual fun write(buffer: ByteArray) {
|
||||||
stdinWriterWrite(writerPtr, buffer)
|
stdinWriterWrite(writerPtr, buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actual fun focusEvent(focused: Boolean) {
|
||||||
|
stdinWriterFocusEvent(writerPtr, focused)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun keyEvent() {
|
||||||
|
stdinWriterKeyEvent(writerPtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun mouseEvent() {
|
||||||
|
stdinWriterMouseEvent(writerPtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun resizeEvent(columns: Int, rows: Int, width: Int, height: Int) {
|
||||||
|
stdinWriterResizeEvent(writerPtr, columns, rows, width, height)
|
||||||
|
}
|
||||||
|
|
||||||
actual override fun close() {
|
actual override fun close() {
|
||||||
stdinWriterFree(writerPtr)
|
reader.close()
|
||||||
|
if (writerPtr != 0L) {
|
||||||
|
stdinWriterFree(writerPtr)
|
||||||
|
writerPtr = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,3 +2,11 @@
|
|||||||
-keep,allowoptimization class com.jakewharton.mosaic.terminal.Jni {
|
-keep,allowoptimization class com.jakewharton.mosaic.terminal.Jni {
|
||||||
native <methods>;
|
native <methods>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# These members are interacted with through native code.
|
||||||
|
-keep,allowoptimization class com.jakewharton.mosaic.terminal.PlatformEventHandler {
|
||||||
|
void onFocus(...);
|
||||||
|
void onKey(...);
|
||||||
|
void onMouse(...);
|
||||||
|
void onResize(...);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,9 +1,20 @@
|
|||||||
package com.jakewharton.mosaic.terminal
|
package com.jakewharton.mosaic.terminal
|
||||||
|
|
||||||
|
import com.jakewharton.mosaic.terminal.event.Event
|
||||||
|
import kotlinx.cinterop.COpaquePointer
|
||||||
import kotlinx.cinterop.CPointer
|
import kotlinx.cinterop.CPointer
|
||||||
|
import kotlinx.cinterop.StableRef
|
||||||
import kotlinx.cinterop.addressOf
|
import kotlinx.cinterop.addressOf
|
||||||
|
import kotlinx.cinterop.alloc
|
||||||
|
import kotlinx.cinterop.asStableRef
|
||||||
|
import kotlinx.cinterop.free
|
||||||
|
import kotlinx.cinterop.nativeHeap
|
||||||
|
import kotlinx.cinterop.ptr
|
||||||
|
import kotlinx.cinterop.staticCFunction
|
||||||
import kotlinx.cinterop.useContents
|
import kotlinx.cinterop.useContents
|
||||||
import kotlinx.cinterop.usePinned
|
import kotlinx.cinterop.usePinned
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
|
||||||
|
|
||||||
public actual object Tty {
|
public actual object Tty {
|
||||||
public actual fun enableRawMode(): AutoCloseable {
|
public actual fun enableRawMode(): AutoCloseable {
|
||||||
@ -24,21 +35,61 @@ public actual object Tty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public actual fun stdinReader(): StdinReader {
|
public actual fun terminalReader(emitDebugEvents: Boolean): TerminalReader {
|
||||||
val reader = stdinReader_init().useContents {
|
val events = Channel<Event>(UNLIMITED)
|
||||||
|
|
||||||
|
val handler = PlatformEventHandler(events)
|
||||||
|
val handlerRef = StableRef.create(handler)
|
||||||
|
val handlerPtr = nativeHeap.alloc<platformEventHandler> {
|
||||||
|
opaque = handlerRef.asCPointer()
|
||||||
|
onFocus = staticCFunction(::onFocusCallback)
|
||||||
|
onKey = staticCFunction(::onKeyCallback)
|
||||||
|
onMouse = staticCFunction(::onMouseCallback)
|
||||||
|
onResize = staticCFunction(::onResizeCallback)
|
||||||
|
}.ptr
|
||||||
|
|
||||||
|
val readerPtr = stdinReader_init(handlerPtr).useContents {
|
||||||
|
reader?.let { return@useContents it }
|
||||||
|
|
||||||
|
nativeHeap.free(handlerPtr)
|
||||||
|
handlerRef.dispose()
|
||||||
|
|
||||||
check(error == 0U) { "Unable to create stdin reader: $error" }
|
check(error == 0U) { "Unable to create stdin reader: $error" }
|
||||||
reader ?: throw OutOfMemoryError()
|
throw OutOfMemoryError()
|
||||||
}
|
}
|
||||||
return StdinReader(reader)
|
|
||||||
|
val reader = PlatformInput(readerPtr, handlerPtr, handlerRef)
|
||||||
|
return TerminalReader(reader, events, emitDebugEvents)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal actual fun stdinWriter(): StdinWriter {
|
internal actual fun stdinWriter(emitDebugEvents: Boolean): StdinWriter {
|
||||||
val writer = stdinWriter_init().useContents {
|
val events = Channel<Event>(UNLIMITED)
|
||||||
|
|
||||||
|
// TODO Fix all this duplication, ownership
|
||||||
|
val handler = PlatformEventHandler(events)
|
||||||
|
val handlerRef = StableRef.create(handler)
|
||||||
|
val handlerPtr = nativeHeap.alloc<platformEventHandler> {
|
||||||
|
opaque = handlerRef.asCPointer()
|
||||||
|
onFocus = staticCFunction(::onFocusCallback)
|
||||||
|
onKey = staticCFunction(::onKeyCallback)
|
||||||
|
onMouse = staticCFunction(::onMouseCallback)
|
||||||
|
onResize = staticCFunction(::onResizeCallback)
|
||||||
|
}.ptr
|
||||||
|
|
||||||
|
val writerPtr = stdinWriter_init(handlerPtr).useContents {
|
||||||
|
writer?.let { return@useContents it }
|
||||||
|
|
||||||
|
nativeHeap.free(handlerPtr)
|
||||||
|
handlerRef.dispose()
|
||||||
|
|
||||||
check(error == 0U) { "Unable to create stdin writer: $error" }
|
check(error == 0U) { "Unable to create stdin writer: $error" }
|
||||||
writer ?: throw OutOfMemoryError()
|
throw OutOfMemoryError()
|
||||||
}
|
}
|
||||||
val reader = stdinWriter_getReader(writer)!!
|
|
||||||
return StdinWriter(writer, reader)
|
val readerPtr = stdinWriter_getReader(writerPtr)!!
|
||||||
|
val platformInput = PlatformInput(readerPtr, handlerPtr, handlerRef)
|
||||||
|
val terminalReader = TerminalReader(platformInput, events, emitDebugEvents)
|
||||||
|
return StdinWriter(writerPtr, terminalReader)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun throwError(error: UInt): Nothing {
|
internal fun throwError(error: UInt): Nothing {
|
||||||
@ -46,38 +97,45 @@ public actual object Tty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public actual class StdinReader internal constructor(
|
internal actual class PlatformInput internal constructor(
|
||||||
private var ref: CPointer<stdinReader>?,
|
ptr: CPointer<stdinReader>,
|
||||||
|
private val handlerPtr: CPointer<platformEventHandler>?,
|
||||||
|
private val handlerRef: StableRef<PlatformEventHandler>?,
|
||||||
) : AutoCloseable {
|
) : AutoCloseable {
|
||||||
public actual fun read(buffer: ByteArray, offset: Int, count: Int): Int {
|
private var ptr: CPointer<stdinReader>? = ptr
|
||||||
|
|
||||||
|
actual fun read(buffer: ByteArray, offset: Int, count: Int): Int {
|
||||||
buffer.usePinned {
|
buffer.usePinned {
|
||||||
stdinReader_read(ref, it.addressOf(offset), count).useContents {
|
stdinReader_read(ptr, it.addressOf(offset), count).useContents {
|
||||||
if (error == 0U) return this.count
|
if (error == 0U) return this.count
|
||||||
Tty.throwError(error)
|
Tty.throwError(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public actual fun readWithTimeout(buffer: ByteArray, offset: Int, count: Int, timeoutMillis: Int): Int {
|
actual fun readWithTimeout(buffer: ByteArray, offset: Int, count: Int, timeoutMillis: Int): Int {
|
||||||
buffer.usePinned {
|
buffer.usePinned {
|
||||||
stdinReader_readWithTimeout(ref, it.addressOf(offset), count, timeoutMillis).useContents {
|
stdinReader_readWithTimeout(ptr, it.addressOf(offset), count, timeoutMillis).useContents {
|
||||||
if (error == 0U) return this.count
|
if (error == 0U) return this.count
|
||||||
Tty.throwError(error)
|
Tty.throwError(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public actual fun interrupt() {
|
actual fun interrupt() {
|
||||||
val error = stdinReader_interrupt(ref)
|
val error = stdinReader_interrupt(ptr)
|
||||||
if (error == 0U) return
|
if (error == 0U) return
|
||||||
Tty.throwError(error)
|
Tty.throwError(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
public actual override fun close() {
|
actual override fun close() {
|
||||||
ref?.let { ref ->
|
ptr?.let { ptr ->
|
||||||
this.ref = null
|
this.ptr = null
|
||||||
|
|
||||||
|
val error = stdinReader_free(ptr)
|
||||||
|
handlerPtr?.let(nativeHeap::free)
|
||||||
|
handlerRef?.dispose()
|
||||||
|
|
||||||
val error = stdinReader_free(ref)
|
|
||||||
if (error == 0U) return
|
if (error == 0U) return
|
||||||
Tty.throwError(error)
|
Tty.throwError(error)
|
||||||
}
|
}
|
||||||
@ -85,22 +143,36 @@ public actual class StdinReader internal constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal actual class StdinWriter internal constructor(
|
internal actual class StdinWriter internal constructor(
|
||||||
private var ref: CPointer<stdinWriter>?,
|
private var ptr: CPointer<stdinWriter>?,
|
||||||
readerRef: CPointer<stdinReader>,
|
actual val reader: TerminalReader,
|
||||||
) : AutoCloseable {
|
) : AutoCloseable {
|
||||||
actual val reader: StdinReader = StdinReader(readerRef)
|
|
||||||
|
|
||||||
actual fun write(buffer: ByteArray) {
|
actual fun write(buffer: ByteArray) {
|
||||||
val error = buffer.usePinned {
|
val error = buffer.usePinned {
|
||||||
stdinWriter_write(ref, it.addressOf(0), buffer.size)
|
stdinWriter_write(ptr, it.addressOf(0), buffer.size)
|
||||||
}
|
}
|
||||||
if (error == 0U) return
|
if (error == 0U) return
|
||||||
Tty.throwError(error)
|
Tty.throwError(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actual fun focusEvent(focused: Boolean) {
|
||||||
|
stdinWriter_focusEvent(ptr, focused)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun keyEvent() {
|
||||||
|
stdinWriter_keyEvent(ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun mouseEvent() {
|
||||||
|
stdinWriter_mouseEvent(ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun resizeEvent(columns: Int, rows: Int, width: Int, height: Int) {
|
||||||
|
stdinWriter_resizeEvent(ptr, columns, rows, width, height)
|
||||||
|
}
|
||||||
|
|
||||||
actual override fun close() {
|
actual override fun close() {
|
||||||
ref?.let { ref ->
|
ptr?.let { ref ->
|
||||||
this.ref = null
|
this.ptr = null
|
||||||
|
|
||||||
reader.close()
|
reader.close()
|
||||||
|
|
||||||
@ -111,3 +183,23 @@ internal actual class StdinWriter internal constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onFocusCallback(opaque: COpaquePointer?, focused: Boolean) {
|
||||||
|
val handler = opaque!!.asStableRef<PlatformEventHandler>().get()
|
||||||
|
handler.onFocus(focused)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onKeyCallback(opaque: COpaquePointer?) {
|
||||||
|
val handler = opaque!!.asStableRef<PlatformEventHandler>().get()
|
||||||
|
handler.onKey()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onMouseCallback(opaque: COpaquePointer?) {
|
||||||
|
val handler = opaque!!.asStableRef<PlatformEventHandler>().get()
|
||||||
|
handler.onMouse()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onResizeCallback(opaque: COpaquePointer?, columns: Int, rows: Int, width: Int, height: Int) {
|
||||||
|
val handler = opaque!!.asStableRef<PlatformEventHandler>().get()
|
||||||
|
handler.onResize(columns, rows, width, height)
|
||||||
|
}
|
||||||
|
|||||||
@ -9,19 +9,15 @@ import com.github.ajalt.clikt.parameters.options.flag
|
|||||||
import com.github.ajalt.clikt.parameters.options.option
|
import com.github.ajalt.clikt.parameters.options.option
|
||||||
import com.github.ajalt.clikt.parameters.types.enum
|
import com.github.ajalt.clikt.parameters.types.enum
|
||||||
import com.jakewharton.finalization.withFinalizationHook
|
import com.jakewharton.finalization.withFinalizationHook
|
||||||
import com.jakewharton.mosaic.terminal.TerminalParser
|
|
||||||
import com.jakewharton.mosaic.terminal.Tty
|
import com.jakewharton.mosaic.terminal.Tty
|
||||||
|
import com.jakewharton.mosaic.terminal.event.DebugEvent
|
||||||
import com.jakewharton.mosaic.terminal.event.KeyboardEvent
|
import com.jakewharton.mosaic.terminal.event.KeyboardEvent
|
||||||
import com.jakewharton.mosaic.terminal.event.KeyboardEvent.Companion.ModifierCtrl
|
import com.jakewharton.mosaic.terminal.event.KeyboardEvent.Companion.ModifierCtrl
|
||||||
import com.jakewharton.mosaic.terminal.event.UnknownEvent
|
|
||||||
import kotlin.jvm.JvmName
|
import kotlin.jvm.JvmName
|
||||||
import kotlinx.coroutines.CoroutineStart.UNDISPATCHED
|
import kotlinx.coroutines.CoroutineStart.UNDISPATCHED
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.IO
|
import kotlinx.coroutines.IO
|
||||||
import kotlinx.coroutines.awaitCancellation
|
import kotlinx.coroutines.awaitCancellation
|
||||||
import kotlinx.coroutines.channels.Channel
|
|
||||||
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
|
|
||||||
import kotlinx.coroutines.job
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
@ -89,7 +85,7 @@ private class RawModeEchoCommand : CliktCommand("raw-mode-echo") {
|
|||||||
print("\u001b]4;$i;?\u001b\\")
|
print("\u001b]4;$i;?\u001b\\")
|
||||||
}
|
}
|
||||||
|
|
||||||
val reader = Tty.stdinReader()
|
val reader = Tty.terminalReader(emitDebugEvents = mode == Mode.Hex)
|
||||||
|
|
||||||
// Upon receiving a signal, this block's job will be canceled. Use that to wake up the
|
// Upon receiving a signal, this block's job will be canceled. Use that to wake up the
|
||||||
// blocking stdin read so it loops and checks if its job is still active or not.
|
// blocking stdin read so it loops and checks if its job is still active or not.
|
||||||
@ -101,52 +97,31 @@ private class RawModeEchoCommand : CliktCommand("raw-mode-echo") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val inputs = Channel<String>(UNLIMITED)
|
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
val job = coroutineContext.job
|
reader.runParseLoop()
|
||||||
|
}
|
||||||
|
|
||||||
|
var first = true
|
||||||
|
val events = reader.events
|
||||||
|
while (true) {
|
||||||
|
val event = events.receive()
|
||||||
|
|
||||||
|
if (!first) print("\r\n")
|
||||||
|
first = false
|
||||||
|
|
||||||
when (mode) {
|
when (mode) {
|
||||||
Mode.Hex -> {
|
Mode.Hex -> print((events.receive() as DebugEvent).bytes.toHexString())
|
||||||
val buffer = ByteArray(1024)
|
Mode.Event -> print(event.toString())
|
||||||
while (job.isActive) {
|
}
|
||||||
val read = reader.read(buffer, 0, 1024)
|
|
||||||
if (read > 0) {
|
if (event is KeyboardEvent &&
|
||||||
val hex = buffer.toHexString(endIndex = read)
|
event.codepoint == 0x63 &&
|
||||||
inputs.trySend(hex)
|
event.modifiers == ModifierCtrl
|
||||||
if (hex == "03" || hex == "1b5b39393b3575") {
|
) {
|
||||||
break
|
break
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Mode.Event -> {
|
|
||||||
val parser = TerminalParser(reader)
|
|
||||||
while (job.isActive) {
|
|
||||||
val (event, bytes) = parser.debugNext()
|
|
||||||
if (event is UnknownEvent) {
|
|
||||||
// The bytes are already displayed by this event.
|
|
||||||
inputs.trySend("$event\r\n")
|
|
||||||
} else {
|
|
||||||
val hex = bytes.toHexString()
|
|
||||||
inputs.trySend("$event\r\n $hex\r\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event is KeyboardEvent &&
|
|
||||||
event.codepoint == 0x63 &&
|
|
||||||
event.modifiers == ModifierCtrl
|
|
||||||
) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
inputs.close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
print(inputs.receive())
|
|
||||||
for (input in inputs) {
|
|
||||||
print("\r\n")
|
|
||||||
print(input)
|
|
||||||
}
|
|
||||||
print("\r\n")
|
print("\r\n")
|
||||||
readerInterruptJob.cancel()
|
readerInterruptJob.cancel()
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user