-
Notifications
You must be signed in to change notification settings - Fork 255
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Issue #422: Adding a native Win32 Terminal implementation, replying o…
…n jna being available
- Loading branch information
Showing
6 changed files
with
524 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
139 changes: 139 additions & 0 deletions
139
src/main/java/com/googlecode/lanterna/terminal/win32/WinDef.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
package com.googlecode.lanterna.terminal.win32; | ||
|
||
import com.sun.jna.Structure; | ||
import com.sun.jna.Structure.FieldOrder; | ||
import com.sun.jna.Union; | ||
|
||
public interface WinDef extends com.sun.jna.platform.win32.WinDef { | ||
|
||
/** | ||
* COORD structure | ||
*/ | ||
@FieldOrder({ "X", "Y" }) | ||
class COORD extends Structure { | ||
|
||
public short X; | ||
public short Y; | ||
|
||
@Override | ||
public String toString() { | ||
return String.format("COORD(%s,%s)", X, Y); | ||
} | ||
} | ||
|
||
/** | ||
* SMALL_RECT structure | ||
*/ | ||
@FieldOrder({ "Left", "Top", "Right", "Bottom" }) | ||
class SMALL_RECT extends Structure { | ||
|
||
public short Left; | ||
public short Top; | ||
public short Right; | ||
public short Bottom; | ||
|
||
@Override | ||
public String toString() { | ||
return String.format("SMALL_RECT(%s,%s)(%s,%s)", Left, Top, Right, Bottom); | ||
} | ||
} | ||
|
||
/** | ||
* CONSOLE_SCREEN_BUFFER_INFO structure | ||
*/ | ||
@FieldOrder({ "dwSize", "dwCursorPosition", "wAttributes", "srWindow", "dwMaximumWindowSize" }) | ||
class CONSOLE_SCREEN_BUFFER_INFO extends Structure { | ||
|
||
public COORD dwSize; | ||
public COORD dwCursorPosition; | ||
public short wAttributes; | ||
public SMALL_RECT srWindow; | ||
public COORD dwMaximumWindowSize; | ||
|
||
@Override | ||
public String toString() { | ||
return String.format("CONSOLE_SCREEN_BUFFER_INFO(%s,%s,%s,%s,%s)", dwSize, dwCursorPosition, wAttributes, srWindow, dwMaximumWindowSize); | ||
} | ||
} | ||
|
||
@FieldOrder({ "EventType", "Event" }) | ||
class INPUT_RECORD extends Structure { | ||
|
||
public static final short KEY_EVENT = 0x01; | ||
public static final short MOUSE_EVENT = 0x02; | ||
public static final short WINDOW_BUFFER_SIZE_EVENT = 0x04; | ||
|
||
public short EventType; | ||
public Event Event; | ||
|
||
public static class Event extends Union { | ||
public KEY_EVENT_RECORD KeyEvent; | ||
public MOUSE_EVENT_RECORD MouseEvent; | ||
public WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent; | ||
} | ||
|
||
@Override | ||
public void read() { | ||
super.read(); | ||
switch (EventType) { | ||
case KEY_EVENT: | ||
Event.setType("KeyEvent"); | ||
break; | ||
case MOUSE_EVENT: | ||
Event.setType("MouseEvent"); | ||
break; | ||
case WINDOW_BUFFER_SIZE_EVENT: | ||
Event.setType("WindowBufferSizeEvent"); | ||
break; | ||
} | ||
Event.read(); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return String.format("INPUT_RECORD(%s)", EventType); | ||
} | ||
} | ||
|
||
@FieldOrder({ "bKeyDown", "wRepeatCount", "wVirtualKeyCode", "wVirtualScanCode", "uChar", "dwControlKeyState" }) | ||
class KEY_EVENT_RECORD extends Structure { | ||
|
||
public boolean bKeyDown; | ||
public short wRepeatCount; | ||
public short wVirtualKeyCode; | ||
public short wVirtualScanCode; | ||
public char uChar; | ||
public int dwControlKeyState; | ||
|
||
@Override | ||
public String toString() { | ||
return String.format("KEY_EVENT_RECORD(%s,%s,%s,%s,%s,%s)", bKeyDown, wRepeatCount, wVirtualKeyCode, wVirtualKeyCode, wVirtualScanCode, uChar, dwControlKeyState); | ||
} | ||
} | ||
|
||
@FieldOrder({ "dwMousePosition", "dwButtonState", "dwControlKeyState", "dwEventFlags" }) | ||
class MOUSE_EVENT_RECORD extends Structure { | ||
|
||
public COORD dwMousePosition; | ||
public int dwButtonState; | ||
public int dwControlKeyState; | ||
public int dwEventFlags; | ||
|
||
@Override | ||
public String toString() { | ||
return String.format("MOUSE_EVENT_RECORD(%s,%s,%s,%s)", dwMousePosition, dwButtonState, dwControlKeyState, dwEventFlags); | ||
} | ||
} | ||
|
||
@FieldOrder({ "dwSize" }) | ||
class WINDOW_BUFFER_SIZE_RECORD extends Structure { | ||
|
||
public COORD dwSize; | ||
|
||
@Override | ||
public String toString() { | ||
return String.format("WINDOW_BUFFER_SIZE_RECORD(%s)", dwSize); | ||
} | ||
} | ||
|
||
} |
24 changes: 24 additions & 0 deletions
24
src/main/java/com/googlecode/lanterna/terminal/win32/Wincon.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package com.googlecode.lanterna.terminal.win32; | ||
|
||
import com.sun.jna.Native; | ||
import com.sun.jna.platform.win32.WinDef.LPVOID; | ||
import com.sun.jna.platform.win32.WinNT.HANDLE; | ||
import com.sun.jna.ptr.IntByReference; | ||
import com.sun.jna.win32.StdCallLibrary; | ||
import com.sun.jna.win32.W32APIOptions; | ||
|
||
public interface Wincon extends StdCallLibrary, com.sun.jna.platform.win32.Wincon { | ||
|
||
Wincon INSTANCE = Native.load("kernel32", Wincon.class, W32APIOptions.UNICODE_OPTIONS); | ||
|
||
int ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004; | ||
int DISABLE_NEWLINE_AUTO_RETURN = 0x0008; | ||
int ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200; | ||
|
||
boolean GetConsoleScreenBufferInfo(HANDLE hConsoleOutput, WinDef.CONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo); | ||
|
||
boolean ReadConsoleInput(HANDLE hConsoleInput, WinDef.INPUT_RECORD[] lpBuffer, int nLength, IntByReference lpNumberOfEventsRead); | ||
|
||
boolean WriteConsole(HANDLE hConsoleOutput, String lpBuffer, int nNumberOfCharsToWrite, IntByReference lpNumberOfCharsWritten, LPVOID lpReserved); | ||
|
||
} |
153 changes: 153 additions & 0 deletions
153
src/main/java/com/googlecode/lanterna/terminal/win32/WindowsConsoleInputStream.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
package com.googlecode.lanterna.terminal.win32; | ||
|
||
import java.io.EOFException; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.nio.ByteBuffer; | ||
import java.nio.CharBuffer; | ||
import java.nio.charset.Charset; | ||
import java.util.Arrays; | ||
import java.util.function.Consumer; | ||
|
||
import com.googlecode.lanterna.terminal.win32.WinDef.INPUT_RECORD; | ||
import com.googlecode.lanterna.terminal.win32.WinDef.KEY_EVENT_RECORD; | ||
import com.googlecode.lanterna.terminal.win32.WinDef.MOUSE_EVENT_RECORD; | ||
import com.googlecode.lanterna.terminal.win32.WinDef.WINDOW_BUFFER_SIZE_RECORD; | ||
import com.sun.jna.platform.win32.WinNT.HANDLE; | ||
import com.sun.jna.ptr.IntByReference; | ||
|
||
public class WindowsConsoleInputStream extends InputStream { | ||
|
||
private final HANDLE hConsoleInput; | ||
private final Charset encoderCharset; | ||
private ByteBuffer buffer = ByteBuffer.allocate(0); | ||
|
||
public WindowsConsoleInputStream(Charset encoderCharset) { | ||
this(Wincon.INSTANCE.GetStdHandle(Wincon.STD_INPUT_HANDLE), encoderCharset); | ||
} | ||
|
||
public WindowsConsoleInputStream(HANDLE hConsoleInput, Charset encoderCharset) { | ||
this.hConsoleInput = hConsoleInput; | ||
this.encoderCharset = encoderCharset; | ||
} | ||
|
||
public HANDLE getHandle() { | ||
return hConsoleInput; | ||
} | ||
|
||
public Charset getEncoderCharset() { | ||
return encoderCharset; | ||
} | ||
|
||
private INPUT_RECORD[] readConsoleInput() throws IOException { | ||
INPUT_RECORD[] lpBuffer = new INPUT_RECORD[64]; | ||
IntByReference lpNumberOfEventsRead = new IntByReference(); | ||
if (Wincon.INSTANCE.ReadConsoleInput(hConsoleInput, lpBuffer, lpBuffer.length, lpNumberOfEventsRead)) { | ||
int n = lpNumberOfEventsRead.getValue(); | ||
return Arrays.copyOfRange(lpBuffer, 0, n); | ||
} | ||
throw new EOFException(); | ||
} | ||
|
||
private int availableConsoleInput() { | ||
IntByReference lpcNumberOfEvents = new IntByReference(); | ||
if (Wincon.INSTANCE.GetNumberOfConsoleInputEvents(hConsoleInput, lpcNumberOfEvents)) { | ||
return lpcNumberOfEvents.getValue(); | ||
} | ||
return 0; | ||
} | ||
|
||
@Override | ||
public synchronized int read() throws IOException { | ||
while (!buffer.hasRemaining()) { | ||
buffer = readKeyEvents(true); | ||
} | ||
|
||
return buffer.get(); | ||
} | ||
|
||
@Override | ||
public synchronized int read(byte[] b, int offset, int length) throws IOException { | ||
while (length > 0 && !buffer.hasRemaining()) { | ||
buffer = readKeyEvents(true); | ||
} | ||
|
||
int n = Math.min(buffer.remaining(), length); | ||
buffer.get(b, offset, n); | ||
return n; | ||
} | ||
|
||
@Override | ||
public synchronized int available() throws IOException { | ||
if (buffer.hasRemaining()) { | ||
return buffer.remaining(); | ||
} | ||
|
||
buffer = readKeyEvents(false); | ||
return buffer.remaining(); | ||
} | ||
|
||
private ByteBuffer readKeyEvents(boolean blocking) throws IOException { | ||
StringBuilder keyEvents = new StringBuilder(); | ||
|
||
if (blocking || availableConsoleInput() > 0) { | ||
for (INPUT_RECORD i : readConsoleInput()) { | ||
filter(i, keyEvents); | ||
} | ||
} | ||
|
||
return encoderCharset.encode(CharBuffer.wrap(keyEvents)); | ||
} | ||
|
||
private void filter(INPUT_RECORD input, Appendable keyEvents) throws IOException { | ||
switch (input.EventType) { | ||
case INPUT_RECORD.KEY_EVENT: | ||
if (input.Event.KeyEvent.uChar != 0 && input.Event.KeyEvent.bKeyDown) { | ||
keyEvents.append(input.Event.KeyEvent.uChar); | ||
} | ||
if (keyEventHandler != null) { | ||
keyEventHandler.accept(input.Event.KeyEvent); | ||
} | ||
break; | ||
case INPUT_RECORD.MOUSE_EVENT: | ||
if (mouseEventHandler != null) { | ||
mouseEventHandler.accept(input.Event.MouseEvent); | ||
} | ||
break; | ||
case INPUT_RECORD.WINDOW_BUFFER_SIZE_EVENT: | ||
if (windowBufferSizeEventHandler != null) { | ||
windowBufferSizeEventHandler.accept(input.Event.WindowBufferSizeEvent); | ||
} | ||
break; | ||
} | ||
} | ||
|
||
private Consumer<KEY_EVENT_RECORD> keyEventHandler = null; | ||
private Consumer<MOUSE_EVENT_RECORD> mouseEventHandler = null; | ||
private Consumer<WINDOW_BUFFER_SIZE_RECORD> windowBufferSizeEventHandler = null; | ||
|
||
public void onKeyEvent(Consumer<KEY_EVENT_RECORD> handler) { | ||
if (keyEventHandler == null) { | ||
keyEventHandler = handler; | ||
} else { | ||
keyEventHandler = keyEventHandler.andThen(handler); | ||
} | ||
} | ||
|
||
public void onMouseEvent(Consumer<MOUSE_EVENT_RECORD> handler) { | ||
if (mouseEventHandler == null) { | ||
mouseEventHandler = handler; | ||
} else { | ||
mouseEventHandler = mouseEventHandler.andThen(handler); | ||
} | ||
} | ||
|
||
public void onWindowBufferSizeEvent(Consumer<WINDOW_BUFFER_SIZE_RECORD> handler) { | ||
if (windowBufferSizeEventHandler == null) { | ||
windowBufferSizeEventHandler = handler; | ||
} else { | ||
windowBufferSizeEventHandler = windowBufferSizeEventHandler.andThen(handler); | ||
} | ||
} | ||
|
||
} |
60 changes: 60 additions & 0 deletions
60
src/main/java/com/googlecode/lanterna/terminal/win32/WindowsConsoleOutputStream.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package com.googlecode.lanterna.terminal.win32; | ||
|
||
import java.io.ByteArrayOutputStream; | ||
import java.io.EOFException; | ||
import java.io.IOException; | ||
import java.io.OutputStream; | ||
import java.nio.charset.Charset; | ||
|
||
import com.sun.jna.platform.win32.WinNT.HANDLE; | ||
import com.sun.jna.ptr.IntByReference; | ||
|
||
public class WindowsConsoleOutputStream extends OutputStream { | ||
|
||
private final HANDLE hConsoleOutput; | ||
private final Charset decoderCharset; | ||
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); | ||
|
||
public WindowsConsoleOutputStream(Charset decoder) { | ||
this(Wincon.INSTANCE.GetStdHandle(Wincon.STD_OUTPUT_HANDLE), decoder); | ||
} | ||
|
||
public WindowsConsoleOutputStream(HANDLE hConsoleOutput, Charset decoderCharset) { | ||
this.hConsoleOutput = hConsoleOutput; | ||
this.decoderCharset = decoderCharset; | ||
} | ||
|
||
public HANDLE getHandle() { | ||
return hConsoleOutput; | ||
} | ||
|
||
public Charset getDecoderCharset() { | ||
return decoderCharset; | ||
} | ||
|
||
|
||
@Override | ||
public synchronized void write(int b) { | ||
buffer.write(b); | ||
} | ||
|
||
@Override | ||
public synchronized void write(byte[] b, int off, int len) { | ||
buffer.write(b, off, len); | ||
} | ||
|
||
@Override | ||
public synchronized void flush() throws IOException { | ||
String characters = buffer.toString(decoderCharset.name()); | ||
buffer.reset(); | ||
|
||
IntByReference lpNumberOfCharsWritten = new IntByReference(); | ||
while (!characters.isEmpty()) { | ||
if (!Wincon.INSTANCE.WriteConsole(hConsoleOutput, characters, characters.length(), lpNumberOfCharsWritten, null)) { | ||
throw new EOFException(); | ||
} | ||
characters = characters.substring(lpNumberOfCharsWritten.getValue()); | ||
} | ||
} | ||
|
||
} |
Oops, something went wrong.