From 13791e2f01ca71f5ca62a5540963a30c72f58e93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 16 Aug 2023 13:49:54 +0200 Subject: [PATCH] Fix read console input on windows with codepage support --- src/crystal/system/win32/file_descriptor.cr | 18 ++++++++++++++++-- src/lib_c/x86_64-windows-msvc/c/consoleapi.cr | 8 ++++++++ src/string/utf16.cr | 8 ++++++-- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 84cfe12c6650..d52fab8fe523 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -11,7 +11,22 @@ module Crystal::System::FileDescriptor @system_blocking = true private def unbuffered_read(slice : Bytes) - if system_blocking? + handle = windows_handle + if LibC.GetConsoleMode(handle, nil) != 0 + u16buffer = Slice(UInt16).new(slice.size) + chars_read = LibC::DWORD.new(0) + if 0 == LibC.ReadConsoleW(handle, u16buffer, u16buffer.size, pointerof(chars_read), nil) + raise IO::Error.from_winerror("Error reading from console") + end + + appender = slice.to_unsafe.appender + String.each_utf16_char(u16buffer[0, chars_read]) do |char| + char.each_byte do |byte| + appender << byte + end + end + chars_read + elsif system_blocking? bytes_read = LibC._read(fd, slice, slice.size) if bytes_read == -1 if Errno.value == Errno::EBADF @@ -22,7 +37,6 @@ module Crystal::System::FileDescriptor end bytes_read else - handle = windows_handle overlapped_operation(handle, "ReadFile", read_timeout) do |overlapped| ret = LibC.ReadFile(handle, slice, slice.size, out byte_count, overlapped) {ret, byte_count} diff --git a/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr b/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr index 680e199be2ab..796369c65a85 100644 --- a/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr @@ -14,6 +14,14 @@ lib LibC fun GetConsoleCP : DWORD fun GetConsoleOutputCP : DWORD + fun ReadConsoleW( + hConsoleInput : HANDLE, + lpBuffer : Void*, + nNumberOfCharsToRead : DWORD, + lpNumberOfCharsRead : DWORD*, + pInputControl : Void* + ) : BOOL + CTRL_C_EVENT = 0 CTRL_BREAK_EVENT = 1 diff --git a/src/string/utf16.cr b/src/string/utf16.cr index b3fdc09301af..f8ce303fa794 100644 --- a/src/string/utf16.cr +++ b/src/string/utf16.cr @@ -128,8 +128,10 @@ class String {string, pointer + 1} end + # :nodoc: + # # Yields each decoded char in the given slice. - private def self.each_utf16_char(slice : Slice(UInt16), &) + def self.each_utf16_char(slice : Slice(UInt16), &) i = 0 while i < slice.size byte = slice[i].to_i @@ -153,8 +155,10 @@ class String end end + # :nodoc: + # # Yields each decoded char in the given pointer, stopping at the first null byte. - private def self.each_utf16_char(pointer : Pointer(UInt16), &) : Pointer(UInt16) + def self.each_utf16_char(pointer : Pointer(UInt16), &) : Pointer(UInt16) loop do byte = pointer.value.to_i break if byte == 0