From 6f367aec7e7c6a7b4bc4c3ef51a15f24aff895a6 Mon Sep 17 00:00:00 2001 From: nathan Date: Sat, 28 May 2016 21:44:59 +0200 Subject: [PATCH] Added scroll up/down for windows --- .../output/WindowsAnsiOutputStream.java | 95 +++++++++++++++---- .../console/util/windows/Kernel32.java | 14 ++- .../console/util/windows/SMALL_RECT.java | 1 + 3 files changed, 88 insertions(+), 22 deletions(-) diff --git a/src/dorkbox/console/output/WindowsAnsiOutputStream.java b/src/dorkbox/console/output/WindowsAnsiOutputStream.java index 3bef1a8..07a0dbe 100644 --- a/src/dorkbox/console/output/WindowsAnsiOutputStream.java +++ b/src/dorkbox/console/output/WindowsAnsiOutputStream.java @@ -31,14 +31,22 @@ package dorkbox.console.output; import static dorkbox.console.util.windows.Kernel32.ASSERT; +import static dorkbox.console.util.windows.Kernel32.FillConsoleOutputAttribute; +import static dorkbox.console.util.windows.Kernel32.FillConsoleOutputCharacterW; +import static dorkbox.console.util.windows.Kernel32.GetConsoleScreenBufferInfo; +import static dorkbox.console.util.windows.Kernel32.SetConsoleCursorPosition; +import static dorkbox.console.util.windows.Kernel32.SetConsoleTextAttribute; import java.io.IOException; import java.io.OutputStream; +import com.sun.jna.ptr.IntByReference; + import dorkbox.console.util.windows.CONSOLE_SCREEN_BUFFER_INFO; import dorkbox.console.util.windows.COORD; import dorkbox.console.util.windows.HANDLE; import dorkbox.console.util.windows.Kernel32; +import dorkbox.console.util.windows.SMALL_RECT; /** * A Windows ANSI escape processor, uses JNA direct-mapping to access native platform API's to change the console attributes. @@ -77,7 +85,7 @@ final class WindowsAnsiOutputStream extends AnsiOutputStream { } private final HANDLE console; - private final CONSOLE_SCREEN_BUFFER_INFO originalInfo = new CONSOLE_SCREEN_BUFFER_INFO(); + private CONSOLE_SCREEN_BUFFER_INFO originalInfo = new CONSOLE_SCREEN_BUFFER_INFO(); private volatile CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO(); private volatile boolean negative; @@ -101,13 +109,13 @@ final class WindowsAnsiOutputStream extends AnsiOutputStream { } out.flush(); - ASSERT(Kernel32.GetConsoleScreenBufferInfo(console, originalInfo), "Could not get the screen info"); + ASSERT(GetConsoleScreenBufferInfo(console, originalInfo), "Could not get the screen info"); } private void getConsoleInfo() throws IOException { out.flush(); - ASSERT(Kernel32.GetConsoleScreenBufferInfo(console, info), "Could not get the screen info:"); + ASSERT(GetConsoleScreenBufferInfo(console, info), "Could not get the screen info:"); } private @@ -124,12 +132,12 @@ final class WindowsAnsiOutputStream extends AnsiOutputStream { attributes = (short) (attributes & 0xFF00 | fg | bg); } - ASSERT(Kernel32.SetConsoleTextAttribute(console, attributes), "Could not set text attributes"); + ASSERT(SetConsoleTextAttribute(console, attributes), "Could not set text attributes"); } private void applyCursorPosition() throws IOException { - ASSERT(Kernel32.SetConsoleCursorPosition(console, info.cursorPosition.asValue()), "Could not set cursor position"); + ASSERT(SetConsoleCursorPosition(console, info.cursorPosition.asValue()), "Could not set cursor position"); } @Override @@ -164,8 +172,8 @@ final class WindowsAnsiOutputStream extends AnsiOutputStream { topLeft.y = info.window.top; int screenLength = info.window.height() * info.size.x; - ASSERT(Kernel32.FillConsoleOutputAttribute(console, originalInfo.attributes, screenLength, topLeft.asValue(), written), "Could not fill console"); - ASSERT(Kernel32.FillConsoleOutputCharacterW(console, ' ', screenLength, topLeft.asValue(), written), "Could not fill console"); + ASSERT(FillConsoleOutputAttribute(console, originalInfo.attributes, screenLength, topLeft.asValue(), written), "Could not fill console"); + ASSERT(FillConsoleOutputCharacterW(console, ' ', screenLength, topLeft.asValue(), written), "Could not fill console"); break; case ERASE_TO_BEGINNING: COORD topLeft2 = new COORD(); @@ -173,14 +181,14 @@ final class WindowsAnsiOutputStream extends AnsiOutputStream { topLeft2.y = info.window.top; int lengthToCursor = (info.cursorPosition.y - info.window.top) * info.size.x + info.cursorPosition.x; - ASSERT(Kernel32.FillConsoleOutputAttribute(console, originalInfo.attributes, lengthToCursor, topLeft2.asValue(), written), "Could not fill console"); - ASSERT(Kernel32.FillConsoleOutputCharacterW(console, ' ', lengthToCursor, topLeft2.asValue(), written), "Could not fill console"); + ASSERT(FillConsoleOutputAttribute(console, originalInfo.attributes, lengthToCursor, topLeft2.asValue(), written), "Could not fill console"); + ASSERT(FillConsoleOutputCharacterW(console, ' ', lengthToCursor, topLeft2.asValue(), written), "Could not fill console"); break; case ERASE_TO_END: int lengthToEnd = (info.window.bottom - info.cursorPosition.y) * info.size.x + info.size.x - info.cursorPosition.x; - ASSERT(Kernel32.FillConsoleOutputAttribute(console, originalInfo.attributes, lengthToEnd, info.cursorPosition.asValue(), written), "Could not fill console"); - ASSERT(Kernel32.FillConsoleOutputCharacterW(console, ' ', lengthToEnd, info.cursorPosition.asValue(), written), "Could not fill console"); + ASSERT(FillConsoleOutputAttribute(console, originalInfo.attributes, lengthToEnd, info.cursorPosition.asValue(), written), "Could not fill console"); + ASSERT(FillConsoleOutputCharacterW(console, ' ', lengthToEnd, info.cursorPosition.asValue(), written), "Could not fill console"); } } @@ -194,21 +202,21 @@ final class WindowsAnsiOutputStream extends AnsiOutputStream { COORD currentRow = info.cursorPosition.asValue(); currentRow.x = (short) 0; - ASSERT(Kernel32.FillConsoleOutputAttribute(console, originalInfo.attributes, info.size.x, currentRow.asValue(), written), "Could not fill console"); - ASSERT(Kernel32.FillConsoleOutputCharacterW(console, ' ', info.size.x, currentRow.asValue(), written), "Could not fill console"); + ASSERT(FillConsoleOutputAttribute(console, originalInfo.attributes, info.size.x, currentRow.asValue(), written), "Could not fill console"); + ASSERT(FillConsoleOutputCharacterW(console, ' ', info.size.x, currentRow.asValue(), written), "Could not fill console"); break; case ERASE_TO_BEGINNING: COORD leftColCurrRow2 = info.cursorPosition.asValue(); leftColCurrRow2.x = (short) 0; - ASSERT(Kernel32.FillConsoleOutputAttribute(console, originalInfo.attributes, info.cursorPosition.x, leftColCurrRow2.asValue(), written), "Could not fill console"); - ASSERT(Kernel32.FillConsoleOutputCharacterW(console, ' ', info.cursorPosition.x, leftColCurrRow2.asValue(), written), "Could not fill console"); + ASSERT(FillConsoleOutputAttribute(console, originalInfo.attributes, info.cursorPosition.x, leftColCurrRow2.asValue(), written), "Could not fill console"); + ASSERT(FillConsoleOutputCharacterW(console, ' ', info.cursorPosition.x, leftColCurrRow2.asValue(), written), "Could not fill console"); break; case ERASE_TO_END: int lengthToLastCol = info.size.x - info.cursorPosition.x; - ASSERT(Kernel32.FillConsoleOutputAttribute(console, originalInfo.attributes, lengthToLastCol, info.cursorPosition.asValue(), written), "Could not fill console"); - ASSERT(Kernel32.FillConsoleOutputCharacterW(console, ' ', lengthToLastCol, info.cursorPosition.asValue(), written), "Could not fill console"); + ASSERT(FillConsoleOutputAttribute(console, originalInfo.attributes, lengthToLastCol, info.cursorPosition.asValue(), written), "Could not fill console"); + ASSERT(FillConsoleOutputCharacterW(console, ' ', lengthToLastCol, info.cursorPosition.asValue(), written), "Could not fill console"); } } @@ -299,20 +307,30 @@ final class WindowsAnsiOutputStream extends AnsiOutputStream { @Override protected - void processScrollDown(final int optionInt) throws IOException { + void processScrollDown(final int count) throws IOException { + scroll((short) -count); } @Override protected - void processScrollUp(final int optionInt) throws IOException { + void processScrollUp(final int count) throws IOException { + scroll((short) count); } protected void processCursorUpLine(final int count) throws IOException { + getConsoleInfo(); + info.cursorPosition.y = (short) Math.max(info.window.top, info.cursorPosition.y - count); + info.cursorPosition.x = 0; + applyCursorPosition(); } protected void processCursorDownLine(final int count) throws IOException { + getConsoleInfo(); + info.cursorPosition.y = (short) Math.max(info.window.top, info.cursorPosition.y + count); + info.cursorPosition.x = 0; + applyCursorPosition(); } @Override @@ -364,6 +382,45 @@ final class WindowsAnsiOutputStream extends AnsiOutputStream { applyCursorPosition(); } + /** + * @param rowsToScroll negative to go down, positive to go up. + * + * Scroll up and new lines are added at the bottom, scroll down and new lines are added at the + * top (per the definition). + * + * Windows doesn't EXACTLY do this, since it will use whatever content is still on the buffer + * and show THAT instead of blank lines. If the content is moved enough so that it runs OFF the + * buffer, blank lines will be shown. + */ + private void scroll(short rowsToScroll) throws IOException { + if (rowsToScroll == 0) { + return; + } + + // Get the current screen buffer window position. + getConsoleInfo(); + + SMALL_RECT.ByReference scrollRect = new SMALL_RECT.ByReference(); + COORD.ByValue coordDest = new COORD.ByValue(); + + // the content that will be scrolled + scrollRect.top = (short) (0); + scrollRect.bottom = (short) (Short.MAX_VALUE); + scrollRect.left = 0; + scrollRect.right = (short) (info.size.x - 1); + + // The destination for the scroll rectangle is xxx row up/down. + coordDest.x = 0; + coordDest.y = (short) (-rowsToScroll); + + // fill the space with whatever color was already there with spaces + IntByReference attribs = new IntByReference(); + attribs.setValue(info.attributes); + + // The clipping rectangle is the same as the scrolling rectangle, so we pass NULL + ASSERT(Kernel32.ScrollConsoleScreenBufferW(console, scrollRect, null, coordDest, attribs), "Could not scroll console"); + } + @Override public void close() throws IOException { super.close(); diff --git a/src/dorkbox/console/util/windows/Kernel32.java b/src/dorkbox/console/util/windows/Kernel32.java index 30e6abe..789ffc0 100644 --- a/src/dorkbox/console/util/windows/Kernel32.java +++ b/src/dorkbox/console/util/windows/Kernel32.java @@ -85,7 +85,7 @@ public class Kernel32 { * https://msdn.microsoft.com/en-us/library/windows/desktop/ms679351(v=vs.85).aspx */ public static native - int FormatMessageA(int flags, Pointer source, int messageId, int languageId, byte[] buffer, int size, long[] args); + int FormatMessageW(int flags, Pointer source, int messageId, int languageId, byte[] buffer, int size, long[] args); /** @@ -95,11 +95,19 @@ public class Kernel32 { int GetConsoleScreenBufferInfo(HANDLE consoleOutput, CONSOLE_SCREEN_BUFFER_INFO consoleScreenBufferInfo); /** - * https://msdn.microsoft.com/en-us/library/ms686025%28VS.85%29.aspx + * https://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx */ public static native int SetConsoleCursorPosition(HANDLE consoleOutput, COORD.ByValue cursorPosition); + /** + * https://msdn.microsoft.com/en-us/library/windows/desktop/ms685107(v=vs.85).aspx + */ + public static native + int ScrollConsoleScreenBufferW(HANDLE consoleOutput, SMALL_RECT.ByReference scrollRect, SMALL_RECT.ByReference clipRect, COORD.ByValue destinationOrigin, IntByReference fillAttributes); + + + /** * https://msdn.microsoft.com/en-us/library/ms682662%28VS.85%29.aspx @@ -148,7 +156,7 @@ public class Kernel32 { int errorCode = Native.getLastError(); int bufferSize = 160; byte data[] = new byte[bufferSize]; - FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, Pointer.NULL, errorCode, 0, data, bufferSize, null); + FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, Pointer.NULL, errorCode, 0, data, bufferSize, null); return new String(data); } diff --git a/src/dorkbox/console/util/windows/SMALL_RECT.java b/src/dorkbox/console/util/windows/SMALL_RECT.java index 5bef4ee..285ebbf 100644 --- a/src/dorkbox/console/util/windows/SMALL_RECT.java +++ b/src/dorkbox/console/util/windows/SMALL_RECT.java @@ -24,6 +24,7 @@ import com.sun.jna.Structure; * https://msdn.microsoft.com/en-us/library/ms686311%28VS.85%29.aspx */ public class SMALL_RECT extends Structure { + static public class ByReference extends SMALL_RECT implements Structure.ByReference { } public short left; public short top;