2019-04-27 08:53:09 +03:00
|
|
|
require 'fiddle/import'
|
|
|
|
|
2019-05-12 18:22:27 +03:00
|
|
|
class Reline::Windows
|
2020-01-12 16:24:17 +03:00
|
|
|
def self.encoding
|
|
|
|
Encoding::UTF_8
|
|
|
|
end
|
|
|
|
|
2020-01-31 20:59:46 +03:00
|
|
|
def self.win?
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
2021-02-17 17:56:34 +03:00
|
|
|
def self.win_legacy_console?
|
|
|
|
@@legacy_console
|
|
|
|
end
|
|
|
|
|
2019-05-31 18:34:38 +03:00
|
|
|
RAW_KEYSTROKE_CONFIG = {
|
2019-06-01 03:05:58 +03:00
|
|
|
[224, 72] => :ed_prev_history, # ↑
|
|
|
|
[224, 80] => :ed_next_history, # ↓
|
|
|
|
[224, 77] => :ed_next_char, # →
|
|
|
|
[224, 75] => :ed_prev_char, # ←
|
|
|
|
[224, 83] => :key_delete, # Del
|
|
|
|
[224, 71] => :ed_move_to_beg, # Home
|
|
|
|
[224, 79] => :ed_move_to_end, # End
|
2019-10-17 20:15:53 +03:00
|
|
|
[ 0, 41] => :ed_unassigned, # input method on/off
|
2019-10-17 20:31:17 +03:00
|
|
|
[ 0, 72] => :ed_prev_history, # ↑
|
|
|
|
[ 0, 80] => :ed_next_history, # ↓
|
|
|
|
[ 0, 77] => :ed_next_char, # →
|
|
|
|
[ 0, 75] => :ed_prev_char, # ←
|
|
|
|
[ 0, 83] => :key_delete, # Del
|
|
|
|
[ 0, 71] => :ed_move_to_beg, # Home
|
2019-10-22 14:42:03 +03:00
|
|
|
[ 0, 79] => :ed_move_to_end # End
|
2019-10-17 20:29:13 +03:00
|
|
|
}
|
2019-05-31 18:34:38 +03:00
|
|
|
|
2019-10-05 17:46:16 +03:00
|
|
|
if defined? JRUBY_VERSION
|
|
|
|
require 'win32api'
|
|
|
|
else
|
|
|
|
class Win32API
|
|
|
|
DLL = {}
|
|
|
|
TYPEMAP = {"0" => Fiddle::TYPE_VOID, "S" => Fiddle::TYPE_VOIDP, "I" => Fiddle::TYPE_LONG}
|
|
|
|
POINTER_TYPE = Fiddle::SIZEOF_VOIDP == Fiddle::SIZEOF_LONG_LONG ? 'q*' : 'l!*'
|
2019-04-30 06:47:40 +03:00
|
|
|
|
2019-10-05 17:46:16 +03:00
|
|
|
WIN32_TYPES = "VPpNnLlIi"
|
|
|
|
DL_TYPES = "0SSI"
|
2019-04-30 06:47:40 +03:00
|
|
|
|
2019-10-05 17:46:16 +03:00
|
|
|
def initialize(dllname, func, import, export = "0", calltype = :stdcall)
|
|
|
|
@proto = [import].join.tr(WIN32_TYPES, DL_TYPES).sub(/^(.)0*$/, '\1')
|
|
|
|
import = @proto.chars.map {|win_type| TYPEMAP[win_type.tr(WIN32_TYPES, DL_TYPES)]}
|
|
|
|
export = TYPEMAP[export.tr(WIN32_TYPES, DL_TYPES)]
|
|
|
|
calltype = Fiddle::Importer.const_get(:CALL_TYPE_TO_ABI)[calltype]
|
2019-04-30 06:47:40 +03:00
|
|
|
|
2019-10-05 17:46:16 +03:00
|
|
|
handle = DLL[dllname] ||=
|
|
|
|
begin
|
|
|
|
Fiddle.dlopen(dllname)
|
|
|
|
rescue Fiddle::DLError
|
|
|
|
raise unless File.extname(dllname).empty?
|
|
|
|
Fiddle.dlopen(dllname + ".dll")
|
|
|
|
end
|
2019-04-30 06:47:40 +03:00
|
|
|
|
2019-10-05 17:46:16 +03:00
|
|
|
@func = Fiddle::Function.new(handle[func], import, export, calltype)
|
|
|
|
rescue Fiddle::DLError => e
|
|
|
|
raise LoadError, e.message, e.backtrace
|
|
|
|
end
|
2019-04-27 08:53:09 +03:00
|
|
|
|
2019-10-05 17:46:16 +03:00
|
|
|
def call(*args)
|
|
|
|
import = @proto.split("")
|
|
|
|
args.each_with_index do |x, i|
|
|
|
|
args[i], = [x == 0 ? nil : x].pack("p").unpack(POINTER_TYPE) if import[i] == "S"
|
|
|
|
args[i], = [x].pack("I").unpack("i") if import[i] == "I"
|
|
|
|
end
|
|
|
|
ret, = @func.call(*args)
|
|
|
|
return ret || 0
|
2019-04-30 06:47:40 +03:00
|
|
|
end
|
2019-04-27 08:53:09 +03:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-05-27 22:25:50 +03:00
|
|
|
VK_MENU = 0x12
|
2019-10-23 20:45:15 +03:00
|
|
|
VK_LMENU = 0xA4
|
2019-10-23 18:12:52 +03:00
|
|
|
VK_CONTROL = 0x11
|
2019-05-27 22:39:29 +03:00
|
|
|
VK_SHIFT = 0x10
|
2019-08-29 15:11:53 +03:00
|
|
|
STD_INPUT_HANDLE = -10
|
2019-04-27 08:53:09 +03:00
|
|
|
STD_OUTPUT_HANDLE = -11
|
2019-08-29 15:11:53 +03:00
|
|
|
WINDOW_BUFFER_SIZE_EVENT = 0x04
|
2020-01-18 20:46:37 +03:00
|
|
|
FILE_TYPE_PIPE = 0x0003
|
|
|
|
FILE_NAME_INFO = 2
|
2019-04-27 08:53:09 +03:00
|
|
|
@@getwch = Win32API.new('msvcrt', '_getwch', [], 'I')
|
|
|
|
@@kbhit = Win32API.new('msvcrt', '_kbhit', [], 'I')
|
|
|
|
@@GetKeyState = Win32API.new('user32', 'GetKeyState', ['L'], 'L')
|
|
|
|
@@GetConsoleScreenBufferInfo = Win32API.new('kernel32', 'GetConsoleScreenBufferInfo', ['L', 'P'], 'L')
|
|
|
|
@@SetConsoleCursorPosition = Win32API.new('kernel32', 'SetConsoleCursorPosition', ['L', 'L'], 'L')
|
|
|
|
@@GetStdHandle = Win32API.new('kernel32', 'GetStdHandle', ['L'], 'L')
|
|
|
|
@@FillConsoleOutputCharacter = Win32API.new('kernel32', 'FillConsoleOutputCharacter', ['L', 'L', 'L', 'L', 'P'], 'L')
|
|
|
|
@@ScrollConsoleScreenBuffer = Win32API.new('kernel32', 'ScrollConsoleScreenBuffer', ['L', 'P', 'P', 'L', 'P'], 'L')
|
|
|
|
@@hConsoleHandle = @@GetStdHandle.call(STD_OUTPUT_HANDLE)
|
2019-08-29 15:11:53 +03:00
|
|
|
@@hConsoleInputHandle = @@GetStdHandle.call(STD_INPUT_HANDLE)
|
|
|
|
@@GetNumberOfConsoleInputEvents = Win32API.new('kernel32', 'GetNumberOfConsoleInputEvents', ['L', 'P'], 'L')
|
|
|
|
@@ReadConsoleInput = Win32API.new('kernel32', 'ReadConsoleInput', ['L', 'P', 'L', 'P'], 'L')
|
2020-01-18 20:46:37 +03:00
|
|
|
@@GetFileType = Win32API.new('kernel32', 'GetFileType', ['L'], 'L')
|
|
|
|
@@GetFileInformationByHandleEx = Win32API.new('kernel32', 'GetFileInformationByHandleEx', ['L', 'I', 'P', 'L'], 'I')
|
2020-08-10 05:01:05 +03:00
|
|
|
@@FillConsoleOutputAttribute = Win32API.new('kernel32', 'FillConsoleOutputAttribute', ['L', 'L', 'L', 'L', 'P'], 'L')
|
2020-01-18 20:46:37 +03:00
|
|
|
|
2021-02-17 17:56:34 +03:00
|
|
|
@@GetConsoleMode = Win32API.new('kernel32', 'GetConsoleMode', ['L', 'P'], 'L')
|
|
|
|
@@SetConsoleMode = Win32API.new('kernel32', 'SetConsoleMode', ['L', 'L'], 'L')
|
|
|
|
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
|
|
|
|
|
|
|
|
private_class_method def self.getconsolemode
|
2021-02-21 11:35:04 +03:00
|
|
|
mode = "\000\000\000\000"
|
2021-02-17 17:56:34 +03:00
|
|
|
@@GetConsoleMode.call(@@hConsoleHandle, mode)
|
|
|
|
mode.unpack1('L')
|
|
|
|
end
|
|
|
|
|
|
|
|
private_class_method def self.setconsolemode(mode)
|
|
|
|
@@SetConsoleMode.call(@@hConsoleHandle, mode)
|
|
|
|
end
|
|
|
|
|
|
|
|
@@legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0)
|
|
|
|
#if @@legacy_console
|
|
|
|
# setconsolemode(getconsolemode() | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
|
|
|
|
# @@legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0)
|
|
|
|
#end
|
|
|
|
|
2019-09-02 00:37:25 +03:00
|
|
|
@@input_buf = []
|
|
|
|
@@output_buf = []
|
2019-04-27 08:53:09 +03:00
|
|
|
|
2020-01-18 20:46:37 +03:00
|
|
|
def self.msys_tty?(io=@@hConsoleInputHandle)
|
|
|
|
# check if fd is a pipe
|
|
|
|
if @@GetFileType.call(io) != FILE_TYPE_PIPE
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
bufsize = 1024
|
|
|
|
p_buffer = "\0" * bufsize
|
|
|
|
res = @@GetFileInformationByHandleEx.call(io, FILE_NAME_INFO, p_buffer, bufsize - 2)
|
|
|
|
return false if res == 0
|
|
|
|
|
|
|
|
# get pipe name: p_buffer layout is:
|
|
|
|
# struct _FILE_NAME_INFO {
|
|
|
|
# DWORD FileNameLength;
|
|
|
|
# WCHAR FileName[1];
|
|
|
|
# } FILE_NAME_INFO
|
|
|
|
len = p_buffer[0, 4].unpack("L")[0]
|
|
|
|
name = p_buffer[4, len].encode(Encoding::UTF_8, Encoding::UTF_16LE, invalid: :replace)
|
|
|
|
|
|
|
|
# Check if this could be a MSYS2 pty pipe ('\msys-XXXX-ptyN-XX')
|
|
|
|
# or a cygwin pty pipe ('\cygwin-XXXX-ptyN-XX')
|
|
|
|
name =~ /(msys-|cygwin-).*-pty/ ? true : false
|
|
|
|
end
|
|
|
|
|
2019-05-12 18:22:27 +03:00
|
|
|
def self.getwch
|
2019-09-02 00:37:25 +03:00
|
|
|
unless @@input_buf.empty?
|
|
|
|
return @@input_buf.shift
|
|
|
|
end
|
2019-04-27 08:53:09 +03:00
|
|
|
while @@kbhit.call == 0
|
|
|
|
sleep(0.001)
|
|
|
|
end
|
|
|
|
until @@kbhit.call == 0
|
|
|
|
ret = @@getwch.call
|
2019-10-17 20:51:12 +03:00
|
|
|
if ret == 0 or ret == 0xE0
|
|
|
|
@@input_buf << ret
|
|
|
|
ret = @@getwch.call
|
|
|
|
@@input_buf << ret
|
|
|
|
return @@input_buf.shift
|
|
|
|
end
|
2019-04-27 08:53:09 +03:00
|
|
|
begin
|
2020-01-12 16:24:17 +03:00
|
|
|
bytes = ret.chr(Encoding::UTF_8).bytes
|
2019-09-02 00:37:25 +03:00
|
|
|
@@input_buf.push(*bytes)
|
2019-04-27 08:53:09 +03:00
|
|
|
rescue Encoding::UndefinedConversionError
|
2019-09-02 00:37:25 +03:00
|
|
|
@@input_buf << ret
|
|
|
|
@@input_buf << @@getwch.call if ret == 224
|
2019-04-27 08:53:09 +03:00
|
|
|
end
|
|
|
|
end
|
2019-09-02 00:37:25 +03:00
|
|
|
@@input_buf.shift
|
2019-04-27 08:53:09 +03:00
|
|
|
end
|
|
|
|
|
2019-05-12 18:22:27 +03:00
|
|
|
def self.getc
|
2019-08-29 15:11:53 +03:00
|
|
|
num_of_events = 0.chr * 8
|
|
|
|
while @@GetNumberOfConsoleInputEvents.(@@hConsoleInputHandle, num_of_events) != 0 and 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
|
|
|
|
event = input_record[0, 2].unpack('s*').first
|
|
|
|
if event == WINDOW_BUFFER_SIZE_EVENT
|
|
|
|
@@winch_handler.()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2019-09-02 00:37:25 +03:00
|
|
|
unless @@output_buf.empty?
|
|
|
|
return @@output_buf.shift
|
2019-04-27 08:53:09 +03:00
|
|
|
end
|
|
|
|
input = getwch
|
2019-10-23 20:45:15 +03:00
|
|
|
meta = (@@GetKeyState.call(VK_LMENU) & 0x80) != 0
|
2019-10-23 18:12:52 +03:00
|
|
|
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
|
2019-05-27 22:39:29 +03:00
|
|
|
# It's treated as Meta+Enter on Windows
|
2019-09-02 00:37:25 +03:00
|
|
|
@@output_buf.push("\e".ord)
|
|
|
|
@@output_buf.push(input)
|
|
|
|
else
|
|
|
|
case input
|
2019-04-27 08:53:09 +03:00
|
|
|
when 0x00
|
2019-10-25 04:07:17 +03:00
|
|
|
meta = false
|
2019-10-17 20:15:53 +03:00
|
|
|
@@output_buf.push(input)
|
2019-04-27 08:53:09 +03:00
|
|
|
input = getwch
|
2019-09-02 00:37:25 +03:00
|
|
|
@@output_buf.push(*input)
|
2019-04-27 08:53:09 +03:00
|
|
|
when 0xE0
|
2019-09-02 00:37:25 +03:00
|
|
|
@@output_buf.push(input)
|
2019-04-27 08:53:09 +03:00
|
|
|
input = getwch
|
2019-09-02 00:37:25 +03:00
|
|
|
@@output_buf.push(*input)
|
2019-04-27 08:53:09 +03:00
|
|
|
when 0x03
|
2019-09-02 00:37:25 +03:00
|
|
|
@@output_buf.push(input)
|
2019-04-27 08:53:09 +03:00
|
|
|
else
|
2019-09-02 00:37:25 +03:00
|
|
|
@@output_buf.push(input)
|
2019-04-27 08:53:09 +03:00
|
|
|
end
|
|
|
|
end
|
2019-10-23 20:45:15 +03:00
|
|
|
if meta
|
2019-04-27 08:53:09 +03:00
|
|
|
"\e".ord
|
|
|
|
else
|
2019-09-02 00:37:25 +03:00
|
|
|
@@output_buf.shift
|
2019-04-27 08:53:09 +03:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-05-24 17:38:40 +03:00
|
|
|
def self.ungetc(c)
|
2019-09-02 00:37:25 +03:00
|
|
|
@@output_buf.unshift(c)
|
2019-05-24 17:38:40 +03:00
|
|
|
end
|
|
|
|
|
2020-10-20 02:39:12 +03:00
|
|
|
def self.in_pasting?
|
|
|
|
not self.empty_buffer?
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.empty_buffer?
|
|
|
|
if not @@input_buf.empty?
|
|
|
|
false
|
|
|
|
elsif @@kbhit.call == 0
|
|
|
|
true
|
|
|
|
else
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-04-27 08:53:09 +03:00
|
|
|
def self.get_screen_size
|
2019-08-29 14:59:29 +03:00
|
|
|
csbi = 0.chr * 22
|
2019-04-27 08:53:09 +03:00
|
|
|
@@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi)
|
2019-05-27 23:10:49 +03:00
|
|
|
csbi[0, 4].unpack('SS').reverse
|
2019-04-27 08:53:09 +03:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.cursor_pos
|
2019-08-29 14:59:29 +03:00
|
|
|
csbi = 0.chr * 22
|
2019-04-27 08:53:09 +03:00
|
|
|
@@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi)
|
|
|
|
x = csbi[4, 2].unpack('s*').first
|
2019-08-29 15:05:19 +03:00
|
|
|
y = csbi[6, 2].unpack('s*').first
|
2019-05-12 18:22:27 +03:00
|
|
|
Reline::CursorPos.new(x, y)
|
2019-04-27 08:53:09 +03:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.move_cursor_column(val)
|
|
|
|
@@SetConsoleCursorPosition.call(@@hConsoleHandle, cursor_pos.y * 65536 + val)
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.move_cursor_up(val)
|
|
|
|
if val > 0
|
2020-12-17 13:57:44 +03:00
|
|
|
y = cursor_pos.y - val
|
|
|
|
y = 0 if y < 0
|
|
|
|
@@SetConsoleCursorPosition.call(@@hConsoleHandle, y * 65536 + cursor_pos.x)
|
2019-04-27 08:53:09 +03:00
|
|
|
elsif val < 0
|
|
|
|
move_cursor_down(-val)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.move_cursor_down(val)
|
|
|
|
if val > 0
|
2020-12-17 13:57:44 +03:00
|
|
|
screen_height = get_screen_size.first
|
|
|
|
y = cursor_pos.y + val
|
|
|
|
y = screen_height - 1 if y > (screen_height - 1)
|
2019-04-27 08:53:09 +03:00
|
|
|
@@SetConsoleCursorPosition.call(@@hConsoleHandle, (cursor_pos.y + val) * 65536 + cursor_pos.x)
|
|
|
|
elsif val < 0
|
|
|
|
move_cursor_up(-val)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.erase_after_cursor
|
|
|
|
csbi = 0.chr * 24
|
|
|
|
@@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi)
|
|
|
|
cursor = csbi[4, 4].unpack('L').first
|
|
|
|
written = 0.chr * 4
|
2019-05-29 12:52:20 +03:00
|
|
|
@@FillConsoleOutputCharacter.call(@@hConsoleHandle, 0x20, get_screen_size.last - cursor_pos.x, cursor, written)
|
2021-01-14 03:16:49 +03:00
|
|
|
@@FillConsoleOutputAttribute.call(@@hConsoleHandle, 0, get_screen_size.last - cursor_pos.x, cursor, written)
|
2019-04-27 08:53:09 +03:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.scroll_down(val)
|
|
|
|
return if val.zero?
|
2020-12-17 13:57:44 +03:00
|
|
|
screen_height = get_screen_size.first
|
|
|
|
val = screen_height - 1 if val > (screen_height - 1)
|
2020-01-30 22:24:44 +03:00
|
|
|
scroll_rectangle = [0, val, get_screen_size.last, get_screen_size.first].pack('s4')
|
2019-04-27 08:53:09 +03:00
|
|
|
destination_origin = 0 # y * 65536 + x
|
|
|
|
fill = [' '.ord, 0].pack('SS')
|
|
|
|
@@ScrollConsoleScreenBuffer.call(@@hConsoleHandle, scroll_rectangle, nil, destination_origin, fill)
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.clear_screen
|
2020-08-10 05:01:05 +03:00
|
|
|
csbi = 0.chr * 22
|
|
|
|
return if @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi) == 0
|
2020-08-30 08:42:41 +03:00
|
|
|
buffer_width = csbi[0, 2].unpack('S').first
|
2020-08-10 05:01:05 +03:00
|
|
|
attributes = csbi[8, 2].unpack('S').first
|
2020-09-12 04:07:18 +03:00
|
|
|
_window_left, window_top, _window_right, window_bottom = *csbi[10,8].unpack('S*')
|
2020-08-30 08:42:41 +03:00
|
|
|
fill_length = buffer_width * (window_bottom - window_top + 1)
|
|
|
|
screen_topleft = window_top * 65536
|
|
|
|
written = 0.chr * 4
|
|
|
|
@@FillConsoleOutputCharacter.call(@@hConsoleHandle, 0x20, fill_length, screen_topleft, written)
|
|
|
|
@@FillConsoleOutputAttribute.call(@@hConsoleHandle, attributes, fill_length, screen_topleft, written)
|
|
|
|
@@SetConsoleCursorPosition.call(@@hConsoleHandle, screen_topleft)
|
2019-04-27 08:53:09 +03:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.set_screen_size(rows, columns)
|
|
|
|
raise NotImplementedError
|
|
|
|
end
|
|
|
|
|
2019-08-29 09:21:00 +03:00
|
|
|
def self.set_winch_handler(&handler)
|
2019-08-29 15:11:53 +03:00
|
|
|
@@winch_handler = handler
|
2019-08-29 09:21:00 +03:00
|
|
|
end
|
|
|
|
|
2019-05-12 18:22:27 +03:00
|
|
|
def self.prep
|
2019-04-27 08:53:09 +03:00
|
|
|
# do nothing
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
|
2019-05-12 18:22:27 +03:00
|
|
|
def self.deprep(otio)
|
2019-04-27 08:53:09 +03:00
|
|
|
# do nothing
|
|
|
|
end
|
|
|
|
end
|