From c59bbd86a6bd0f1ce8a7babf56feabeb41a7e675 Mon Sep 17 00:00:00 2001 From: aycabta Date: Fri, 16 Apr 2021 19:59:36 +0900 Subject: [ruby/reline] Use ReadConsoleInputW() instead of getwch() This needs https://github.com/aycabta/yamatanooroti/pull/19, which is released by yamatanooroti gem 0.0.7, to test with yamatanooroti. https://github.com/ruby/reline/commit/06c1f45da1 --- lib/reline/windows.rb | 136 +++++++++++++++++++++++++++----------------------- 1 file changed, 74 insertions(+), 62 deletions(-) (limited to 'lib/reline') diff --git a/lib/reline/windows.rb b/lib/reline/windows.rb index c645458cfc..50cfe3f7df 100644 --- a/lib/reline/windows.rb +++ b/lib/reline/windows.rb @@ -89,9 +89,30 @@ class Reline::Windows VK_LMENU = 0xA4 VK_CONTROL = 0x11 VK_SHIFT = 0x10 + + KEY_EVENT = 0x01 + WINDOW_BUFFER_SIZE_EVENT = 0x04 + + CAPSLOCK_ON = 0x0080 + ENHANCED_KEY = 0x0100 + LEFT_ALT_PRESSED = 0x0002 + LEFT_CTRL_PRESSED = 0x0008 + NUMLOCK_ON = 0x0020 + RIGHT_ALT_PRESSED = 0x0001 + RIGHT_CTRL_PRESSED = 0x0004 + SCROLLLOCK_ON = 0x0040 + SHIFT_PRESSED = 0x0010 + + VK_END = 0x23 + VK_HOME = 0x24 + VK_LEFT = 0x25 + VK_UP = 0x26 + VK_RIGHT = 0x27 + VK_DOWN = 0x28 + VK_DELETE = 0x2E + STD_INPUT_HANDLE = -10 STD_OUTPUT_HANDLE = -11 - WINDOW_BUFFER_SIZE_EVENT = 0x04 FILE_TYPE_PIPE = 0x0003 FILE_NAME_INFO = 2 @@getwch = Win32API.new('msvcrt', '_getwch', [], 'I') @@ -105,7 +126,7 @@ class Reline::Windows @@hConsoleHandle = @@GetStdHandle.call(STD_OUTPUT_HANDLE) @@hConsoleInputHandle = @@GetStdHandle.call(STD_INPUT_HANDLE) @@GetNumberOfConsoleInputEvents = Win32API.new('kernel32', 'GetNumberOfConsoleInputEvents', ['L', 'P'], 'L') - @@ReadConsoleInput = Win32API.new('kernel32', 'ReadConsoleInput', ['L', 'P', 'L', 'P'], 'L') + @@ReadConsoleInputW = Win32API.new('kernel32', 'ReadConsoleInputW', ['L', 'P', 'L', 'P'], 'L') @@GetFileType = Win32API.new('kernel32', 'GetFileType', ['L'], 'L') @@GetFileInformationByHandleEx = Win32API.new('kernel32', 'GetFileInformationByHandleEx', ['L', 'I', 'P', 'L'], 'I') @@FillConsoleOutputAttribute = Win32API.new('kernel32', 'FillConsoleOutputAttribute', ['L', 'L', 'L', 'L', 'P'], 'L') @@ -157,78 +178,69 @@ class Reline::Windows name =~ /(msys-|cygwin-).*-pty/ ? true : false end - def self.getwch - unless @@input_buf.empty? - return @@input_buf.shift - end - while @@kbhit.call == 0 - sleep(0.001) - end - until @@kbhit.call == 0 - ret = @@getwch.call - if ret == 0 or ret == 0xE0 - @@input_buf << ret - ret = @@getwch.call - @@input_buf << ret - return @@input_buf.shift - end - begin - bytes = ret.chr(Encoding::UTF_8).bytes - @@input_buf.push(*bytes) - rescue Encoding::UndefinedConversionError - @@input_buf << ret - @@input_buf << @@getwch.call if ret == 224 + def self.process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state) + char = char_code.chr(Encoding::UTF_8) + if char_code == 0x0D and control_key_state.anybits?(LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED | SHIFT_PRESSED) + # It's treated as Meta+Enter on Windows. + @@output_buf.push("\e".ord) + @@output_buf.push(char_code) + elsif control_key_state.anybits?(LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) + @@output_buf.push("\e".ord) + @@output_buf.concat(char.bytes) + elsif control_key_state.anybits?(ENHANCED_KEY) + case virtual_key_code # Emulate getwch() key sequences. + when VK_END + @@output_buf.push(0, 79) + when VK_HOME + @@output_buf.push(0, 71) + when VK_LEFT + @@output_buf.push(0, 75) + when VK_UP + @@output_buf.push(0, 72) + when VK_RIGHT + @@output_buf.push(0, 77) + when VK_DOWN + @@output_buf.push(0, 80) + when VK_DELETE + @@output_buf.push(0, 83) end + elsif char_code == 0 and control_key_state != 0 + # unknown + else + @@output_buf.concat(char.bytes) end - @@input_buf.shift end - def self.getc + def self.check_input_event num_of_events = 0.chr * 8 - while @@GetNumberOfConsoleInputEvents.(@@hConsoleInputHandle, num_of_events) != 0 and num_of_events.unpack('L').first > 0 + while @@output_buf.empty? #or true + next if @@GetNumberOfConsoleInputEvents.(@@hConsoleInputHandle, num_of_events) == 0 or num_of_events.unpack('L').first == 0 input_record = 0.chr * 18 read_event = 0.chr * 4 - if @@ReadConsoleInput.(@@hConsoleInputHandle, input_record, 1, read_event) != 0 + if @@ReadConsoleInputW.(@@hConsoleInputHandle, input_record, 1, read_event) != 0 event = input_record[0, 2].unpack('s*').first - if event == WINDOW_BUFFER_SIZE_EVENT + case event + when WINDOW_BUFFER_SIZE_EVENT @@winch_handler.() + when KEY_EVENT + key_down = input_record[4, 4].unpack('l*').first + repeat_count = input_record[8, 2].unpack('s*').first + virtual_key_code = input_record[10, 2].unpack('s*').first + virtual_scan_code = input_record[12, 2].unpack('s*').first + char_code = input_record[14, 2].unpack('S*').first + control_key_state = input_record[16, 2].unpack('S*').first + is_key_down = key_down.zero? ? false : true + if is_key_down + process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state) + end end end end - unless @@output_buf.empty? - return @@output_buf.shift - end - input = getwch - meta = (@@GetKeyState.call(VK_LMENU) & 0x80) != 0 - control = (@@GetKeyState.call(VK_CONTROL) & 0x80) != 0 - shift = (@@GetKeyState.call(VK_SHIFT) & 0x80) != 0 - force_enter = !input.instance_of?(Array) && (control or shift) && input == 0x0D - if force_enter - # It's treated as Meta+Enter on Windows - @@output_buf.push("\e".ord) - @@output_buf.push(input) - else - case input - when 0x00 - meta = false - @@output_buf.push(input) - input = getwch - @@output_buf.push(*input) - when 0xE0 - @@output_buf.push(input) - input = getwch - @@output_buf.push(*input) - when 0x03 - @@output_buf.push(input) - else - @@output_buf.push(input) - end - end - if meta - "\e".ord - else - @@output_buf.shift - end + end + + def self.getc + check_input_event + @@output_buf.shift end def self.ungetc(c) -- cgit v1.2.3