From 124d3064cd20c65f567696a671b7bb0083a6b124 Mon Sep 17 00:00:00 2001 From: Jean-Francois Denise Date: Tue, 12 Jun 2018 13:18:18 +0200 Subject: [PATCH] Fix char encoding when writing directly to Windows console --- .../impl/AbstractWindowsTerminal.java | 32 ++++++++++++------- .../terminal/impl/WinSysTerminal.java | 2 +- .../tty/terminal/TerminalConnection.java | 8 +++-- .../src/main/java/org/aesh/io/Encoder.java | 11 ++++--- .../main/java/org/aesh/terminal/Terminal.java | 4 +++ 5 files changed, 39 insertions(+), 18 deletions(-) diff --git a/readline/src/main/java/org/aesh/readline/terminal/impl/AbstractWindowsTerminal.java b/readline/src/main/java/org/aesh/readline/terminal/impl/AbstractWindowsTerminal.java index 6ce55983..19340209 100644 --- a/readline/src/main/java/org/aesh/readline/terminal/impl/AbstractWindowsTerminal.java +++ b/readline/src/main/java/org/aesh/readline/terminal/impl/AbstractWindowsTerminal.java @@ -35,10 +35,15 @@ import java.io.PipedOutputStream; import java.io.PrintWriter; import java.io.StringWriter; +import java.nio.CharBuffer; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; +import java.util.function.Consumer; import java.util.logging.Level; +import java.util.logging.Logger; +import org.aesh.io.Encoder; +import org.aesh.readline.util.LoggerUtil; import org.aesh.terminal.tty.Signal; import org.aesh.terminal.tty.Size; import static org.fusesource.jansi.internal.Kernel32.GetStdHandle; @@ -48,16 +53,18 @@ abstract class AbstractWindowsTerminal extends AbstractTerminal { - private static class ConsoleOutput extends OutputStream { + private static class ConsoleOutput implements Consumer { + private static final Logger LOGGER = LoggerUtil.getLogger(AbstractWindowsTerminal.class.getName()); private static final long console = GetStdHandle(STD_OUTPUT_HANDLE); private final int[] writtenChars = new int[1]; @Override - public void write(int b) throws IOException { - char[] chars = new char[]{(char) b}; - if (WriteConsoleW(console, chars, 1, writtenChars, 0) == 0) { - throw new IOException("Failed to write to console: " + WindowsSupport.getLastErrorMessage()); + public void accept(int[] input) { + CharBuffer buffer = Encoder.toCharBuffer(input); + char[] chars = buffer.array(); + if (WriteConsoleW(console, chars, chars.length, writtenChars, 0) == 0) { + LOGGER.log(Level.WARNING, "Failed to write out.", WindowsSupport.getLastErrorMessage()); } } @@ -82,17 +89,15 @@ public void write(int b) throws IOException { protected final Thread pump; private volatile boolean closing; + private final ConsoleOutput cpConsumer; - public AbstractWindowsTerminal(String name, boolean nativeSignals, SignalHandler signalHandler) throws IOException { - this(null, name, nativeSignals, signalHandler); - } - - public AbstractWindowsTerminal(OutputStream output, String name, boolean nativeSignals, SignalHandler signalHandler) throws IOException { + public AbstractWindowsTerminal(boolean consumeCP, OutputStream output, String name, boolean nativeSignals, SignalHandler signalHandler) throws IOException { super(name, "windows", signalHandler); PipedInputStream input = new PipedInputStream(PIPE_SIZE); this.slaveInputPipe = new PipedOutputStream(input); this.input = new FilterInputStream(input) {}; - this.output = output == null ? new ConsoleOutput() : output; + this.cpConsumer = consumeCP ? new ConsoleOutput() : null; + this.output = output; String encoding = getConsoleEncoding(); if (encoding == null) { encoding = Charset.defaultCharset().name(); @@ -116,6 +121,11 @@ public AbstractWindowsTerminal(OutputStream output, String name, boolean nativeS ShutdownHooks.add(closer); } + @Override + public Consumer getCodePointConsumer() { + return cpConsumer; + } + @Override protected void handleDefaultSignal(Signal signal) { Object handler = nativeHandlers.get(signal); diff --git a/readline/src/main/java/org/aesh/readline/terminal/impl/WinSysTerminal.java b/readline/src/main/java/org/aesh/readline/terminal/impl/WinSysTerminal.java index 9c6d47a3..b284cdc4 100644 --- a/readline/src/main/java/org/aesh/readline/terminal/impl/WinSysTerminal.java +++ b/readline/src/main/java/org/aesh/readline/terminal/impl/WinSysTerminal.java @@ -43,7 +43,7 @@ public WinSysTerminal(String name, boolean nativeSignals) throws IOException { } public WinSysTerminal(String name, boolean nativeSignals, SignalHandler signalHandler) throws IOException { - super(setVTMode() ? null : new WindowsAnsiOutputStream(new FileOutputStream(FileDescriptor.out)), name, nativeSignals, signalHandler); + super(setVTMode(), new WindowsAnsiOutputStream(new FileOutputStream(FileDescriptor.out)), name, nativeSignals, signalHandler); } protected int getConsoleOutputCP() { diff --git a/readline/src/main/java/org/aesh/readline/tty/terminal/TerminalConnection.java b/readline/src/main/java/org/aesh/readline/tty/terminal/TerminalConnection.java index f083d5b7..cd9c120d 100644 --- a/readline/src/main/java/org/aesh/readline/tty/terminal/TerminalConnection.java +++ b/readline/src/main/java/org/aesh/readline/tty/terminal/TerminalConnection.java @@ -59,7 +59,7 @@ public class TerminalConnection implements Connection { private Consumer sizeHandler; private Decoder decoder; - private Encoder stdOut; + private Consumer stdOut; private Attributes attributes; private EventDecoder eventDecoder; private volatile boolean reading = false; @@ -140,8 +140,12 @@ private void init(Terminal term) { eventDecoder = new EventDecoder(attributes); decoder = new Decoder(512, inputEncoding(), eventDecoder); - stdOut = new Encoder(outputEncoding(), this::write); + if(terminal.getCodePointConsumer() == null) { + stdOut = new Encoder(outputEncoding(), this::write); + } else { + stdOut = terminal.getCodePointConsumer(); + } if(terminal instanceof ExternalTerminal) ansi = false; diff --git a/terminal-api/src/main/java/org/aesh/io/Encoder.java b/terminal-api/src/main/java/org/aesh/io/Encoder.java index c41d9772..5ec6e5b8 100644 --- a/terminal-api/src/main/java/org/aesh/io/Encoder.java +++ b/terminal-api/src/main/java/org/aesh/io/Encoder.java @@ -48,7 +48,12 @@ public void setCharset(Charset charset) { @Override public void accept(int[] input) { - final char[] tmp = new char[2]; + ByteBuffer bytesBuf = charset.encode(toCharBuffer(input)); + out.accept(safeTrim(bytesBuf.array(), bytesBuf.limit())); + } + + public static CharBuffer toCharBuffer(int[] input) { + final char[] tmp = new char[2]; int capacity = 0; for (int codePoint : input) { capacity += Character.charCount(codePoint); @@ -59,9 +64,7 @@ public void accept(int[] input) { charBuf.put(tmp, 0, size); } charBuf.flip(); - ByteBuffer bytesBuf = charset.encode(charBuf); - - out.accept(safeTrim(bytesBuf.array(), bytesBuf.limit())); + return charBuf; } private static byte[] safeTrim(byte[] bytes, int length) { diff --git a/terminal-api/src/main/java/org/aesh/terminal/Terminal.java b/terminal-api/src/main/java/org/aesh/terminal/Terminal.java index 974ff8c9..679ac264 100644 --- a/terminal-api/src/main/java/org/aesh/terminal/Terminal.java +++ b/terminal-api/src/main/java/org/aesh/terminal/Terminal.java @@ -22,6 +22,7 @@ import java.io.Closeable; import java.io.InputStream; import java.io.OutputStream; +import java.util.function.Consumer; import org.aesh.terminal.tty.Signal; import org.aesh.terminal.tty.Size; @@ -57,4 +58,7 @@ interface SignalHandler { // Infocmp capabilities Device device(); + default Consumer getCodePointConsumer() { + return null; + } }