module Yamatanooroti::WindowsTestCaseModule
Constants
- Bottom
- DL
- EventType
- Left
- Right
- Top
- UnicodeChar
Public Instance Methods
assert_screen(expected_lines)
click to toggle source
# File lib/yamatanooroti/windows.rb, line 487 def assert_screen(expected_lines) case expected_lines when Array assert_equal(expected_lines, @result) when String assert_equal(expected_lines, @result.join("\n").sub(/\n*\z/, "\n")) end end
close()
click to toggle source
# File lib/yamatanooroti/windows.rb, line 446 def close sleep @wait # read first before kill the console process including output @result = retrieve_screen free_resources end
result()
click to toggle source
# File lib/yamatanooroti/windows.rb, line 483 def result @result end
start_terminal(height, width, command, wait: 1, startup_message: nil)
click to toggle source
# File lib/yamatanooroti/windows.rb, line 496 def start_terminal(height, width, command, wait: 1, startup_message: nil) @height = height @width = width @wait = wait @result = nil setup_console(height, width) launch(command.map{ |c| quote_command_arg(c) }.join(' ')) case startup_message when String check_startup_message = ->(message) { message.start_with?(startup_message) } when Regexp check_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 end end
write(str)
click to toggle source
# File lib/yamatanooroti/windows.rb, line 368 def write(str) sleep @wait str.force_encoding(Encoding::ASCII_8BIT).tr!("\n", "\r") records = Fiddle::Pointer.malloc(DL::INPUT_RECORD_WITH_KEY_EVENT.size * str.size * 2, DL::FREE) str.chars.each_with_index do |c, i| byte = c.ord if c.bytesize == 1 and byte.allbits?(0x80) # with Meta key c = (byte ^ 0x80).chr control_key_state = DL::LEFT_ALT_PRESSED else control_key_state = 0 end record_index = i * 2 r = DL::INPUT_RECORD_WITH_KEY_EVENT.new(records + DL::INPUT_RECORD_WITH_KEY_EVENT.size * record_index) set_input_record(r, c, true, control_key_state) record_index = i * 2 + 1 r = DL::INPUT_RECORD_WITH_KEY_EVENT.new(records + DL::INPUT_RECORD_WITH_KEY_EVENT.size * record_index) set_input_record(r, c, false, control_key_state) end written_size = Fiddle::Pointer.malloc(Fiddle::SIZEOF_DWORD, DL::FREE) r = DL.WriteConsoleInputW(DL.GetStdHandle(DL::STD_INPUT_HANDLE), records, str.size * 2, written_size) error_message(r, 'WriteConsoleInput') end
Private Instance Methods
error_message(r, method_name)
click to toggle source
# File lib/yamatanooroti/windows.rb, line 344 def error_message(r, method_name) return if not r.zero? err = DL.GetLastError string = Fiddle::Pointer.malloc(Fiddle::SIZEOF_VOIDP) DL.FormatMessage( DL::FORMAT_MESSAGE_ALLOCATE_BUFFER | DL::FORMAT_MESSAGE_FROM_SYSTEM, Fiddle::NULL, err, 0x0, string, 0, Fiddle::NULL ) log "ERROR(#{method_name}): #{err.to_s}: #{string.ptr.to_s}" DL.LocalFree(string) end
free_resources()
click to toggle source
# File lib/yamatanooroti/windows.rb, line 407 def free_resources h_snap = DL.CreateToolhelp32Snapshot(DL::TH32CS_SNAPPROCESS, 0) pe = DL::PROCESSENTRY32W.malloc (pe.to_ptr + 0)[0, DL::PROCESSENTRY32W.size] = "\x00" * DL::PROCESSENTRY32W.size pe.dwSize = DL::PROCESSENTRY32W.size r = DL.Process32FirstW(h_snap, pe) error_message(r, "Process32First") process_table = {} loop do #log "a #{pe.th32ParentProcessID.inspect} -> #{pe.th32ProcessID.inspect} #{wc2mb(pe.szExeFile.pack('S260')).unpack('Z*').pack('Z*')}" process_table[pe.th32ParentProcessID] ||= [] process_table[pe.th32ParentProcessID] << pe.th32ProcessID break if DL.Process32NextW(h_snap, pe).zero? end process_table[DL.GetCurrentProcessId].each do |child_pid| kill_process_tree(process_table, child_pid) end #r = DL.TerminateThread(@pi.hThread, 0) #error_message(r, "TerminateThread") #sleep @wait r = DL.FreeConsole() #error_message(r, "FreeConsole") r = DL.AttachConsole(DL::ATTACH_PARENT_PROCESS) error_message(r, 'AttachConsole') end
full_width?(c)
click to toggle source
# File lib/yamatanooroti/windows.rb, line 278 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
kill_process_tree(process_table, pid)
click to toggle source
# File lib/yamatanooroti/windows.rb, line 433 def kill_process_tree(process_table, pid) process_table[pid]&.each do |child_pid| kill_process_tree(process_table, child_pid) end h_proc = DL.OpenProcess(DL::PROCESS_ALL_ACCESS, 0, pid) if (h_proc) r = DL.TerminateProcess(h_proc, 0) error_message(r, "TerminateProcess") r = DL.CloseHandle(h_proc) error_message(r, "CloseHandle") end end
launch(command)
click to toggle source
# File lib/yamatanooroti/windows.rb, line 326 def launch(command) command = %Q{cmd.exe /q /c "#{command}"} converted_command = mb2wc(command) @pi = DL::PROCESS_INFORMATION.malloc (@pi.to_ptr + 0)[0, DL::PROCESS_INFORMATION.size] = "\x00" * DL::PROCESS_INFORMATION.size @startup_info_ex = DL::STARTUPINFOW.malloc (@startup_info_ex.to_ptr + 0)[0, DL::STARTUPINFOW.size] = "\x00" * DL::STARTUPINFOW.size r = DL.CreateProcessW( Fiddle::NULL, converted_command, Fiddle::NULL, Fiddle::NULL, 0, 0, Fiddle::NULL, Fiddle::NULL, @startup_info_ex, @pi ) error_message(r, 'CreateProcessW') sleep @wait rescue => e pp e end
log(str)
click to toggle source
# File lib/yamatanooroti/windows.rb, line 361 def log(str) puts str open('aaa', 'a') do |fp| fp.puts str end end
mb2wc(str)
click to toggle source
# File lib/yamatanooroti/windows.rb, line 264 def mb2wc(str) size = DL.MultiByteToWideChar(65001, 0, str, str.bytesize, '', 0) converted_str = String.new("\x00" * (size * 2), encoding: 'ASCII-8BIT') DL.MultiByteToWideChar(65001, 0, str, str.bytesize, converted_str, converted_str.bytesize) converted_str end
quote_command_arg(arg)
click to toggle source
# File lib/yamatanooroti/windows.rb, line 297 def quote_command_arg(arg) if not arg.match?(/[ \t"]/) # No quotation needed. return arg end if not arg.match?(/["\\]/) # No embedded double quotes or backlashes, so I can just wrap quote # marks around the whole thing. return %{"#{arg}"} end quote_hit = true result = '"' arg.chars.reverse.each do |c| result << c if quote_hit and c == '\\' result << '\\' elsif c == '"' quote_hit = true result << '\\' else quote_hit = false end end result << '"' result.reverse end
retrieve_screen()
click to toggle source
# File lib/yamatanooroti/windows.rb, line 454 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 region.Bottom = @height 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(/ *$/, '') end screen end
set_input_record(r, c, key_down, control_key_state)
click to toggle source
# File lib/yamatanooroti/windows.rb, line 392 def set_input_record(r, c, key_down, control_key_state) begin code = c.unpack('U').first rescue ArgumentError code = c.bytes.first end r.EventType = DL::KEY_EVENT r.bKeyDown = key_down ? 1 : 0 r.wRepeatCount = 1 r.wVirtualKeyCode = DL.VkKeyScanW(code) r.wVirtualScanCode = DL.MapVirtualKeyW(code, 0) r.UnicodeChar = code r.dwControlKeyState = control_key_state end
setup_console(height, width)
click to toggle source
# File lib/yamatanooroti/windows.rb, line 223 def setup_console(height, width) r = DL.FreeConsole error_message(r, 'FreeConsole') r = DL.AllocConsole error_message(r, 'AllocConsole') @output_handle = DL.GetStdHandle(DL::STD_OUTPUT_HANDLE) =begin font = DL::CONSOLE_FONT_INFOEX.malloc (font.to_ptr + 0)[0, DL::CONSOLE_FONT_INFOEX.size] = "\x00" * DL::CONSOLE_FONT_INFOEX.size font.cbSize = DL::CONSOLE_FONT_INFOEX.size font.nFont = 0 font_size = 72 font.dwFontSize = font_size * 65536 + font_size font.FontFamily = 0 font.FontWeight = 0 font.FaceName[0] = "\x00".ord r = DL.SetCurrentConsoleFontEx(@output_handle, 0, font) error_message(r, 'SetCurrentConsoleFontEx') =end rect = DL::SMALL_RECT.malloc rect.Left = 0 rect.Top = 0 rect.Right = width - 1 rect.Bottom = height - 1 r = DL.SetConsoleWindowInfo(@output_handle, 1, rect) error_message(r, 'SetConsoleWindowInfo') size = DL.GetSystemMetrics(DL::SM_CYMIN) * 65536 + DL.GetSystemMetrics(DL::SM_CXMIN) r = DL.SetConsoleScreenBufferSize(@output_handle, size) error_message(r, 'SetConsoleScreenBufferSize') size = height * 65536 + width r = DL.SetConsoleScreenBufferSize(@output_handle, size) error_message(r, 'SetConsoleScreenBufferSize') r = DL.ShowWindow(DL.GetConsoleWindow(), DL::SW_HIDE) error_message(r, 'ShowWindow') end
wc2mb(str)
click to toggle source
# File lib/yamatanooroti/windows.rb, line 271 def wc2mb(str) size = DL.WideCharToMultiByte(65001, 0, str, str.bytesize, '', 0, 0, 0) converted_str = "\x00" * (size * 2) DL.WideCharToMultiByte(65001, 0, str, str.bytesize, converted_str, converted_str.bytesize, 0, 0) converted_str end