Skip to content

Commit

Permalink
get working again on windows
Browse files Browse the repository at this point in the history
Use ReadConsoleOutputCharacterW instead of ReadConsoleOutputW
- ReadConsoleOutputW reads multicolum character wrong (probably when raster font used)
Propery terminate commandline in launch()
wait_startup_message() and assert_screen from vterm.rb 415a438
  • Loading branch information
YO4 committed Sep 11, 2024
1 parent 415a438 commit 5b6a1d8
Showing 1 changed file with 61 additions and 75 deletions.
136 changes: 61 additions & 75 deletions lib/yamatanooroti/windows.rb
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,6 @@ module Yamatanooroti::WindowsDefinition
STD_ERROR_HANDLE = -12
ATTACH_PARENT_PROCESS = -1
KEY_EVENT = 0x0001
CT_CTYPE3 = 0x04
C3_HIRAGANA = 0x0020
C3_HALFWIDTH = 0x0040
C3_FULLWIDTH = 0x0080
C3_IDEOGRAPH = 0x0100
TH32CS_SNAPPROCESS = 0x00000002
PROCESS_ALL_ACCESS = 0x001FFFFF
SW_HIDE = 0
Expand Down Expand Up @@ -183,8 +178,8 @@ module Yamatanooroti::WindowsDefinition
extern 'SHORT VkKeyScanW(WCHAR);', :stdcall
# UINT MapVirtualKeyW(UINT uCode, UINT uMapType);
extern 'UINT MapVirtualKeyW(UINT, UINT);', :stdcall
# BOOL ReadConsoleOutputW(HANDLE hConsoleOutput, PCHAR_INFO lpBuffer, COORD dwBufferSize, COORD dwBufferCoord, PSMALL_RECT lpReadRegion);
extern 'BOOL ReadConsoleOutputW(HANDLE, PCHAR_INFO, COORD, COORD, PSMALL_RECT);', :stdcall
# BOOL WINAPI ReadConsoleOutputCharacterW(HANDLE hConsoleOutput, LPWSTR lpCharacter, DWORD nLength, COORD dwReadCoord, LPDWORD lpNumberOfCharsRead);
extern 'BOOL ReadConsoleOutputCharacterW(HANDLE, LPWSTR, DWORD, COORD, LPDWORD);', :stdcall
# BOOL WINAPI GetConsoleScreenBufferInfo(HANDLE hConsoleOutput, PCONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo);
extern 'BOOL GetConsoleScreenBufferInfo(HANDLE, PCONSOLE_SCREEN_BUFFER_INFO);', :stdcall
# BOOL WINAPI GetCurrentConsoleFontEx(HANDLE hConsoleOutput, BOOL bMaximumWindow, PCONSOLE_FONT_INFOEX lpConsoleCurrentFontEx);
Expand Down Expand Up @@ -213,8 +208,6 @@ module Yamatanooroti::WindowsDefinition
extern 'int MultiByteToWideChar(UINT, DWORD, LPCSTR, int, LPWSTR, int);', :stdcall
# int WideCharToMultiByte(UINT CodePage, DWORD dwFlags, _In_NLS_string_(cchWideChar)LPCWCH lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, LPCCH lpDefaultChar, LPBOOL lpUsedDefaultChar);
extern 'int WideCharToMultiByte(UINT, DWORD, LPCWCH, int, LPSTR, int, LPCCH, LPBOOL);', :stdcall
#BOOL GetStringTypeW(DWORD dwInfoType, LPCWCH lpSrcStr, int cchSrc, LPWORD lpCharType);
extern 'BOOL GetStringTypeW(DWORD, LPCWCH, int, LPWORD);', :stdcall

typealias 'LPTSTR', 'void*'
typealias 'HLOCAL', 'HANDLE'
Expand Down Expand Up @@ -310,25 +303,6 @@ module Yamatanooroti::WindowsTestCaseModule
converted_str
end

private def full_width?(c)
return false if c.nil? or c.empty?
wc = mb2wc(c)
type = Fiddle::Pointer.malloc(Fiddle::SIZEOF_WORD, DL::FREE)
DL.GetStringTypeW(DL::CT_CTYPE3, wc, wc.bytesize, type)
char_type = type[0, Fiddle::SIZEOF_WORD].unpack('S').first
if char_type.anybits?(DL::C3_FULLWIDTH)
true
elsif char_type.anybits?(DL::C3_HALFWIDTH)
false
elsif char_type.anybits?(DL::C3_HIRAGANA)
true
elsif char_type.anybits?(DL::C3_IDEOGRAPH)
true
else
false
end
end

private def quote_command_arg(arg)
if not arg.match?(/[ \t"]/)
# No quotation needed.
Expand Down Expand Up @@ -359,7 +333,7 @@ module Yamatanooroti::WindowsTestCaseModule
end

private def launch(command)
command = %Q{cmd.exe /q /c "#{command}"}
command = "#{command}\0"
converted_command = mb2wc(command)
@pi = DL::PROCESS_INFORMATION.malloc
(@pi.to_ptr + 0)[0, DL::PROCESS_INFORMATION.size] = "\x00" * DL::PROCESS_INFORMATION.size
Expand Down Expand Up @@ -479,82 +453,94 @@ def write(str)
end

def close
sleep @wait
sleep 0.3
# read first before kill the console process including output
@result = retrieve_screen

free_resources
end

private def retrieve_screen
char_info_matrix = Fiddle::Pointer.to_ptr("\x00" * (DL::CHAR_INFO.size * (@height * @width)))
region = DL::SMALL_RECT.malloc
region.Left = 0
region.Top = 0
region.Right = @width - 1
region.Bottom = @height - 1
r = DL.ReadConsoleOutputW(@output_handle, char_info_matrix, @height * 65536 + @width, 0, region)
error_message(r, "ReadConsoleOutputW")
screen = []
prev_c = nil
@height.times do |y|
line = +''
@width.times do |x|
index = @width * y + x
char_info = DL::CHAR_INFO.new(char_info_matrix + DL::CHAR_INFO.size * index)
mb = [char_info.UnicodeChar].pack('U')
if prev_c == mb and full_width?(mb)
prev_c = nil
else
line << mb
prev_c = mb
end
end
screen << line.gsub(/ *$/, '')
def retrieve_screen
buffer_chars = @width * 8
buffer = Fiddle::Pointer.malloc(Fiddle::SIZEOF_SHORT * buffer_chars, DL::FREE)
n = Fiddle::Pointer[0]
lines = (0...@height).map do |y|
r = DL.ReadConsoleOutputCharacterW(@output_handle, buffer, @width, y << 16, -n)
error_message(r, "ReadConsoleOutputCharacterW")
r == 0 ? "" : wc2mb(buffer[0, n.to_i * 2]).gsub(/ *$/, "")
end
screen
lines
end

def result
@result || retrieve_screen
end

private def assert_screen_with_proc(check_proc, assert_block, convert_proc = :itself.to_proc)
retry_until = Time.now + @timeout
while Time.now < retry_until
#break unless try_sync

break if check_proc.call(convert_proc.call(result))
sleep @wait
end
sleep @wait
assert_block.call(convert_proc.call(result))
end

def assert_screen(expected_lines, message = nil)
actual_lines = result
actual_string = actual_lines.join("\n").sub(/\n*\z/, "\n")
lines_to_string = ->(lines) { lines.join("\n").sub(/\n*\z/, "\n") }
case expected_lines
when Array
assert_equal(expected_lines, actual_lines, message)
assert_screen_with_proc(
->(a) { expected_lines == a },
->(a) { assert_equal(expected_lines, a, message) }
)
when String
assert_equal(expected_lines, actual_string, message)
assert_screen_with_proc(
->(a) { expected_lines == a },
->(a) { assert_equal(expected_lines, a, message) },
lines_to_string
)
when Regexp
assert_match(expected_lines, actual_string, message)
assert_screen_with_proc(
->(a) { expected_lines.match?(a) },
->(a) { assert_match(expected_lines, a, message) },
lines_to_string
)
end
end

def start_terminal(height, width, command, wait: 1, startup_message: nil)
@height = height
@width = width
def start_terminal(height, width, command, wait: 0.2, timeout: 2, startup_message: nil)
@timeout = timeout
@wait = wait
@result = nil

@height = height
@width = width
setup_console(height, width)
system("cmd /c cls")
launch(command.map{ |c| quote_command_arg(c) }.join(' '))

case startup_message
when String
check_startup_message = ->(message) {
message.start_with?(
startup_message.each_char.each_slice(width).map(&:join).join("\0").gsub(/ *\0/, "\n")
)
}
wait_startup_message { |message| message.start_with?(startup_message) }
when Regexp
check_startup_message = ->(message) { startup_message.match?(message) }
wait_startup_message { |message| startup_message.match?(message) }
end
if check_startup_message
loop do
screen = retrieve_screen.join("\n").sub(/\n*\z/, "\n")
break if check_startup_message.(screen)
sleep @wait
end

private def wait_startup_message
wait_until = Time.now + @timeout
chunks = +''
loop do
wait = wait_until - Time.now
if wait.negative?
raise "Startup message didn't arrive within timeout: #{chunks.inspect}"
end

chunks = retrieve_screen.join("\n").sub(/\n*\z/, "\n")
break if yield chunks
end
end
end
Expand Down

0 comments on commit 5b6a1d8

Please sign in to comment.