io-console 0.8.2-java → 0.9.1-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/ffi/io/console/bsd_console.rb +3 -1
- data/lib/ffi/io/console/common.rb +20 -1
- data/lib/ffi/io/console/linux_console.rb +3 -1
- data/lib/ffi/io/console/native_console.rb +55 -16
- data/lib/ffi/io/console/stty_console.rb +32 -28
- data/lib/ffi/io/console/version.rb +4 -2
- data/lib/ffi/io/console/windows_console.rb +293 -0
- data/lib/ffi/io/console/windows_constants.rb +26 -0
- data/lib/ffi/io/console.rb +30 -41
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e86817ae95f9a6fc3fa3301ea895706a3071f8e500c3c2dec9ef669dae510609
|
|
4
|
+
data.tar.gz: 433bb663068ef08dbc2a076c0d037d94872f2906652bd198ee950fff5c3a1bc7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ec7274caecc4e434b7a1e7afe30251d1b07f677c54d3865f3b341c008f96ca6a7d95faa7c87faacbbf65734ee7a0e23bbdfb469088349a03c1cacc5f82be3bee
|
|
7
|
+
data.tar.gz: a9b72f79b80a65809217ed7cdab1edeb01daa53ffe9647d42900960569f4db3e83bb869c80f8da8ee9b2c76d23a66b08cc5d879a373a9ac9d9f6bdd05c0c8a66
|
|
@@ -6,7 +6,7 @@ if RbConfig::CONFIG['host_os'].downcase =~ /darwin/ && FFI::Platform::ARCH !~ /#
|
|
|
6
6
|
raise LoadError.new("native console on MacOS only supported on #{tested_platforms.join(', ')}")
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
module IO::LibC
|
|
9
|
+
module IO::Console::LibC
|
|
10
10
|
extend FFI::Library
|
|
11
11
|
ffi_lib FFI::Library::LIBC
|
|
12
12
|
|
|
@@ -170,3 +170,5 @@ module IO::LibC
|
|
|
170
170
|
attach_function :tcflush, [ :int, :int ], :int
|
|
171
171
|
attach_function :ioctl, [ :int, :ulong, :varargs ], :int
|
|
172
172
|
end
|
|
173
|
+
|
|
174
|
+
require_relative 'native_console'
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
# Methods common to all backend impls
|
|
2
|
+
require 'io/wait'
|
|
3
|
+
|
|
4
|
+
module IO::Console
|
|
5
|
+
end
|
|
6
|
+
|
|
2
7
|
class IO
|
|
3
8
|
# TODO: Windows version uses "conin$" and "conout$" instead of /dev/tty
|
|
4
9
|
def self.console(sym = nil, *args)
|
|
@@ -62,12 +67,26 @@ class IO
|
|
|
62
67
|
str.chomp
|
|
63
68
|
end
|
|
64
69
|
|
|
70
|
+
def input_pending?
|
|
71
|
+
!wait_readable(0).nil?
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def hide_cursor
|
|
75
|
+
write "\e[?25l"
|
|
76
|
+
self
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def show_cursor
|
|
80
|
+
write "\e[?25h"
|
|
81
|
+
self
|
|
82
|
+
end
|
|
83
|
+
|
|
65
84
|
def cursor
|
|
66
85
|
raw do
|
|
67
86
|
syswrite "\e[6n"
|
|
68
87
|
|
|
69
88
|
return nil if getbyte != 0x1b
|
|
70
|
-
return nil if getbyte !=
|
|
89
|
+
return nil if getbyte != "[".ord
|
|
71
90
|
|
|
72
91
|
num = 0
|
|
73
92
|
result = []
|
|
@@ -6,7 +6,7 @@ unless FFI::Platform::ARCH =~ /#{tested_platforms.join('|')}/
|
|
|
6
6
|
warn "native console only tested on #{tested_platforms.join(', ')}"
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
module IO::LibC
|
|
9
|
+
module IO::Console::LibC
|
|
10
10
|
extend FFI::Library
|
|
11
11
|
ffi_lib FFI::Library::LIBC
|
|
12
12
|
|
|
@@ -202,3 +202,5 @@ module IO::LibC
|
|
|
202
202
|
attach_function :tcflush, [ :int, :int ], :int
|
|
203
203
|
attach_function :ioctl, [ :int, :ulong, :varargs ], :int
|
|
204
204
|
end
|
|
205
|
+
|
|
206
|
+
require_relative 'native_console'
|
|
@@ -1,15 +1,5 @@
|
|
|
1
|
-
# Load appropriate native bits for BSD or Linux
|
|
2
|
-
case RbConfig::CONFIG['host_os'].downcase
|
|
3
|
-
when /darwin|openbsd|freebsd|netbsd/
|
|
4
|
-
require_relative 'bsd_console'
|
|
5
|
-
when /linux/
|
|
6
|
-
require_relative 'linux_console'
|
|
7
|
-
else
|
|
8
|
-
raise LoadError.new("no native io/console support")
|
|
9
|
-
end
|
|
10
|
-
|
|
11
1
|
# Common logic that uses native calls for console
|
|
12
|
-
|
|
2
|
+
module IO::Console
|
|
13
3
|
def ttymode
|
|
14
4
|
termios = LibC::Termios.new
|
|
15
5
|
if LibC.tcgetattr(self.fileno, termios) != 0
|
|
@@ -18,21 +8,26 @@ class IO
|
|
|
18
8
|
|
|
19
9
|
if block_given?
|
|
20
10
|
yield tmp = termios.dup
|
|
21
|
-
|
|
22
|
-
raise SystemCallError.new(respond_to?(:path) ? path : "tcsetattr(TCSANOW)", FFI.errno)
|
|
23
|
-
end
|
|
11
|
+
ttymode_set(tmp)
|
|
24
12
|
end
|
|
25
13
|
termios
|
|
26
14
|
end
|
|
27
15
|
private :ttymode
|
|
28
16
|
|
|
17
|
+
def ttymode_set(termios)
|
|
18
|
+
if LibC.tcsetattr(self.fileno, LibC::TCSANOW, termios) != 0
|
|
19
|
+
raise SystemCallError.new(respond_to?(:path) ? path : "tcsetattr(TCSANOW)", FFI.errno)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
private :ttymode_set
|
|
23
|
+
|
|
29
24
|
def ttymode_yield(block, **opts, &setup)
|
|
30
25
|
begin
|
|
31
26
|
orig_termios = ttymode { |t| setup.call(t, **opts) }
|
|
32
27
|
block.call(self)
|
|
33
28
|
ensure
|
|
34
|
-
if orig_termios
|
|
35
|
-
|
|
29
|
+
if orig_termios
|
|
30
|
+
ttymode_set(orig_termios)
|
|
36
31
|
end
|
|
37
32
|
end
|
|
38
33
|
end
|
|
@@ -93,6 +88,46 @@ class IO
|
|
|
93
88
|
ttymode_yield(block) { |t| t[:c_lflag] &= ~(TTY_ECHO) }
|
|
94
89
|
end
|
|
95
90
|
|
|
91
|
+
class Mode
|
|
92
|
+
attr_reader :termios
|
|
93
|
+
|
|
94
|
+
def initialize(t)
|
|
95
|
+
@termios = t
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def initialize_copy(m)
|
|
99
|
+
@termios = m.termios.dup
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def echo=(echo)
|
|
103
|
+
if echo
|
|
104
|
+
@termios[:c_lflag] |= TTY_ECHO
|
|
105
|
+
else
|
|
106
|
+
@termios[:c_lflag] &= ~TTY_ECHO
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def raw!(min: 1, time: nil, intr: nil)
|
|
111
|
+
TTY_RAW[@termios, min:, time:, intr:]
|
|
112
|
+
self
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def raw(min: 1, time: nil, intr: nil)
|
|
116
|
+
new_mode = dup
|
|
117
|
+
TTY_RAW[new_mode.termios, min:, time:, intr:]
|
|
118
|
+
new_mode
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def console_mode
|
|
123
|
+
Mode.new(ttymode)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def console_mode=(mode)
|
|
127
|
+
ttymode_set(mode.termios)
|
|
128
|
+
mode
|
|
129
|
+
end
|
|
130
|
+
|
|
96
131
|
def winsize
|
|
97
132
|
ws = LibC::Winsize.new
|
|
98
133
|
if LibC.ioctl(self.fileno, LibC::TIOCGWINSZ, :pointer, ws.pointer) != 0
|
|
@@ -138,3 +173,7 @@ class IO
|
|
|
138
173
|
raise SystemCallError.new("tcflush(TCIOFLUSH)", FFI.errno) unless LibC.tcflush(self.fileno, LibC::TCIOFLUSH) == 0
|
|
139
174
|
end
|
|
140
175
|
end
|
|
176
|
+
|
|
177
|
+
class IO
|
|
178
|
+
include Console
|
|
179
|
+
end
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# attempt to call stty; if failure, raise error
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
module IO::Console
|
|
3
|
+
STTY = %w[/usr/bin/stty /bin/stty].find {|path| File.executable?(path)}
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
unless IO::Console::STTY &&
|
|
7
|
+
system(IO::Console::STTY, out: File::NULL, err: %i[child out])
|
|
4
8
|
raise "stty command returned nonzero exit status"
|
|
5
9
|
end
|
|
6
10
|
|
|
@@ -8,46 +12,45 @@ warn "io/console on JRuby shells out to stty for most operations" if $VERBOSE
|
|
|
8
12
|
|
|
9
13
|
# Non-Windows assumes stty command is available
|
|
10
14
|
class IO
|
|
11
|
-
|
|
12
|
-
protected def _io_console_stty(*args)
|
|
13
|
-
_io_console_stty_error { `stty #{args.join(' ')} < /proc/#{Process.pid}/fd/#{fileno}` }
|
|
14
|
-
end
|
|
15
|
-
else
|
|
16
|
-
protected def _io_console_stty(*args)
|
|
17
|
-
_io_console_stty_error { `stty #{args.join(' ')}` }
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
protected def _io_console_stty_error
|
|
15
|
+
private def _io_console_stty(*args)
|
|
22
16
|
# pre-check to catch non-tty filenos we can't stty against anyway
|
|
23
17
|
raise Errno::ENOTTY, inspect if !tty?
|
|
24
18
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
19
|
+
IO.pipe do |re, we|
|
|
20
|
+
input = dup # workaround for JRuby
|
|
21
|
+
IO.popen([Console::STTY, *args, in: input, err: we], &:read)
|
|
22
|
+
ensure
|
|
23
|
+
input&.close
|
|
24
|
+
unless $?.success?
|
|
25
|
+
we.close
|
|
26
|
+
error = re.read
|
|
27
|
+
case error
|
|
28
|
+
when /Inappropriate ioctl for device/
|
|
29
|
+
raise Errno::ENOTTY, inspect
|
|
30
|
+
end
|
|
31
|
+
raise "stty command failed: #{error.chomp}"
|
|
32
|
+
end
|
|
30
33
|
end
|
|
31
|
-
|
|
32
|
-
result
|
|
33
34
|
end
|
|
34
35
|
|
|
35
36
|
def raw(*, min: 1, time: nil, intr: nil)
|
|
36
|
-
saved = _io_console_stty('-g
|
|
37
|
+
saved = _io_console_stty('-g')
|
|
38
|
+
_io_console_stty('raw')
|
|
37
39
|
yield self
|
|
38
40
|
ensure
|
|
39
|
-
_io_console_stty(saved)
|
|
41
|
+
_io_console_stty(saved) if saved
|
|
40
42
|
end
|
|
41
43
|
|
|
42
44
|
def raw!(*)
|
|
43
|
-
|
|
45
|
+
_io_console_stty('raw')
|
|
44
46
|
end
|
|
45
47
|
|
|
46
48
|
def cooked(*)
|
|
47
|
-
saved = _io_console_stty('-g'
|
|
49
|
+
saved = _io_console_stty('-g')
|
|
50
|
+
_io_console_stty('-raw')
|
|
48
51
|
yield self
|
|
49
52
|
ensure
|
|
50
|
-
_io_console_stty(saved)
|
|
53
|
+
_io_console_stty(saved) if saved
|
|
51
54
|
end
|
|
52
55
|
|
|
53
56
|
def cooked!(*)
|
|
@@ -63,10 +66,11 @@ class IO
|
|
|
63
66
|
end
|
|
64
67
|
|
|
65
68
|
def noecho
|
|
66
|
-
saved = _io_console_stty('-g'
|
|
69
|
+
saved = _io_console_stty('-g')
|
|
70
|
+
_io_console_stty('-echo')
|
|
67
71
|
yield self
|
|
68
72
|
ensure
|
|
69
|
-
_io_console_stty(saved)
|
|
73
|
+
_io_console_stty(saved) if saved
|
|
70
74
|
end
|
|
71
75
|
|
|
72
76
|
# Not all systems return same format of stty -a output
|
|
@@ -74,7 +78,7 @@ class IO
|
|
|
74
78
|
UBUNTU = 'rows (?<rows>\d+); columns (?<columns>\d+)'
|
|
75
79
|
|
|
76
80
|
def winsize
|
|
77
|
-
match = _io_console_stty('-a').match(/#{IEEE_STD_1003_2}|#{UBUNTU}/)
|
|
81
|
+
match = _io_console_stty('-a').match(/#{IEEE_STD_1003_2}|#{UBUNTU}/o)
|
|
78
82
|
[match[:rows].to_i, match[:columns].to_i]
|
|
79
83
|
end
|
|
80
84
|
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
require 'ffi'
|
|
2
|
+
|
|
3
|
+
module IO::Console::Windows
|
|
4
|
+
STD_INPUT_HANDLE = -10
|
|
5
|
+
STD_OUTPUT_HANDLE = -11
|
|
6
|
+
WAIT_OBJECT_0 = 0
|
|
7
|
+
WAIT_TIMEOUT = 258
|
|
8
|
+
ENABLE_WRAP_AT_EOL_OUTPUT = 2
|
|
9
|
+
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
|
|
10
|
+
|
|
11
|
+
module Native
|
|
12
|
+
extend FFI::Library
|
|
13
|
+
ffi_convention :stdcall
|
|
14
|
+
ffi_lib 'kernel32'
|
|
15
|
+
attach_function :GetStdHandle, [:int32], :pointer
|
|
16
|
+
attach_function :GetConsoleMode, [:pointer, :pointer], :int
|
|
17
|
+
attach_function :SetConsoleMode, [:pointer, :uint32], :int
|
|
18
|
+
attach_function :WaitForSingleObject, [:pointer, :uint32], :uint32
|
|
19
|
+
attach_function :ReadConsoleInputW,
|
|
20
|
+
[:pointer, :pointer, :uint32, :pointer], :int
|
|
21
|
+
attach_function :GetFileType, [:pointer], :uint32
|
|
22
|
+
attach_function :GetFileInformationByHandleEx,
|
|
23
|
+
[:pointer, :int, :pointer, :uint32], :int
|
|
24
|
+
attach_function :GetConsoleScreenBufferInfo, [:pointer, :pointer], :int
|
|
25
|
+
attach_function :SetConsoleCursorPosition, [:pointer, :uint32], :int
|
|
26
|
+
attach_function :FillConsoleOutputCharacterW,
|
|
27
|
+
[:pointer, :uint16, :uint32, :uint32, :pointer], :int
|
|
28
|
+
attach_function :FillConsoleOutputAttribute,
|
|
29
|
+
[:pointer, :uint16, :uint32, :uint32, :pointer], :int
|
|
30
|
+
attach_function :GetConsoleCursorInfo, [:pointer, :pointer], :int
|
|
31
|
+
attach_function :SetConsoleCursorInfo, [:pointer, :pointer], :int
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
module CRT
|
|
35
|
+
extend FFI::Library
|
|
36
|
+
ffi_convention :cdecl
|
|
37
|
+
ffi_lib 'msvcrt'
|
|
38
|
+
attach_function :_kbhit, [], :int
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
GetStdHandle = Native.method(:GetStdHandle)
|
|
42
|
+
GetConsoleMode = Native.method(:GetConsoleMode)
|
|
43
|
+
SetConsoleMode = Native.method(:SetConsoleMode)
|
|
44
|
+
WaitForSingleObject = Native.method(:WaitForSingleObject)
|
|
45
|
+
ReadConsoleInputW = Native.method(:ReadConsoleInputW)
|
|
46
|
+
GetFileType = Native.method(:GetFileType)
|
|
47
|
+
GetFileInformationByHandleEx = Native.method(:GetFileInformationByHandleEx)
|
|
48
|
+
GetConsoleScreenBufferInfo = Native.method(:GetConsoleScreenBufferInfo)
|
|
49
|
+
SetConsoleCursorPosition = Native.method(:SetConsoleCursorPosition)
|
|
50
|
+
FillConsoleOutputCharacter = Native.method(:FillConsoleOutputCharacterW)
|
|
51
|
+
FillConsoleOutputAttribute = Native.method(:FillConsoleOutputAttribute)
|
|
52
|
+
GetConsoleCursorInfo = Native.method(:GetConsoleCursorInfo)
|
|
53
|
+
SetConsoleCursorInfo = Native.method(:SetConsoleCursorInfo)
|
|
54
|
+
Kbhit = CRT.method(:_kbhit)
|
|
55
|
+
|
|
56
|
+
INPUT_HANDLE = GetStdHandle.call(STD_INPUT_HANDLE)
|
|
57
|
+
OUTPUT_HANDLE = GetStdHandle.call(STD_OUTPUT_HANDLE)
|
|
58
|
+
|
|
59
|
+
module_function
|
|
60
|
+
|
|
61
|
+
def handle(io)
|
|
62
|
+
io.equal?(STDIN) ? INPUT_HANDLE : OUTPUT_HANDLE
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def console_mode(io)
|
|
66
|
+
buffer = "\0" * 4
|
|
67
|
+
raise SystemCallError, 'GetConsoleMode' if GetConsoleMode.call(handle(io), buffer) == 0
|
|
68
|
+
buffer.unpack1('L')
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def set_console_mode(io, mode)
|
|
72
|
+
raise SystemCallError, 'SetConsoleMode' if SetConsoleMode.call(handle(io), mode) == 0
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def screen_buffer_info(io)
|
|
76
|
+
buffer = "\0" * 22
|
|
77
|
+
raise SystemCallError, 'GetConsoleScreenBufferInfo' if GetConsoleScreenBufferInfo.call(handle(io), buffer) == 0
|
|
78
|
+
buffer.unpack('s9')
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def coordinate(x, y)
|
|
82
|
+
(y & 0xffff) << 16 | (x & 0xffff)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
module IO::Console::Windows::TTY
|
|
87
|
+
def tty?(*types)
|
|
88
|
+
return super() if types.empty?
|
|
89
|
+
|
|
90
|
+
default = msys = cygwin = false
|
|
91
|
+
types.each do |type|
|
|
92
|
+
case type
|
|
93
|
+
when nil
|
|
94
|
+
default = true
|
|
95
|
+
when :any
|
|
96
|
+
default = msys = cygwin = true
|
|
97
|
+
when :msys
|
|
98
|
+
msys = true
|
|
99
|
+
when :cygwin
|
|
100
|
+
cygwin = true
|
|
101
|
+
when Symbol
|
|
102
|
+
raise ArgumentError, "unknown tty type: #{type.inspect}"
|
|
103
|
+
else
|
|
104
|
+
raise TypeError, "expected Symbol, got #{type.class}"
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
return true if default && super()
|
|
109
|
+
(msys || cygwin) && msys_tty?(msys, cygwin)
|
|
110
|
+
end
|
|
111
|
+
alias isatty tty?
|
|
112
|
+
|
|
113
|
+
private def msys_tty?(msys, cygwin)
|
|
114
|
+
windows = IO::Console::Windows
|
|
115
|
+
handle = windows.handle(self)
|
|
116
|
+
return false unless windows::GetFileType.call(handle) == 3
|
|
117
|
+
buffer = "\0" * 1024
|
|
118
|
+
return false if windows::GetFileInformationByHandleEx.call(handle, 2, buffer, 1022) == 0
|
|
119
|
+
length = buffer.unpack1('L')
|
|
120
|
+
name = buffer[4, length].encode(Encoding::UTF_8, Encoding::UTF_16LE, invalid: :replace)
|
|
121
|
+
return false unless (msys && name.start_with?('\\msys-')) || (cygwin && name.start_with?('\\cygwin-'))
|
|
122
|
+
name.include?('-pty')
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
IO.prepend(IO::Console::Windows::TTY)
|
|
127
|
+
|
|
128
|
+
class IO::ConsoleMode
|
|
129
|
+
def initialize(mode)
|
|
130
|
+
@mode = mode
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def virtual_terminal_processing?
|
|
134
|
+
@mode & IO::Console::Windows::ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def virtual_terminal_processing=(enabled)
|
|
138
|
+
set_flag(IO::Console::Windows::ENABLE_VIRTUAL_TERMINAL_PROCESSING, enabled)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def wrap_at_eol_output?
|
|
142
|
+
@mode & IO::Console::Windows::ENABLE_WRAP_AT_EOL_OUTPUT != 0
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def wrap_at_eol_output=(enabled)
|
|
146
|
+
set_flag(IO::Console::Windows::ENABLE_WRAP_AT_EOL_OUTPUT, enabled)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
private def set_flag(flag, enabled)
|
|
150
|
+
enabled ? @mode |= flag : @mode &= ~flag
|
|
151
|
+
self
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
private def to_i
|
|
155
|
+
@mode
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
class IO
|
|
160
|
+
def console_mode
|
|
161
|
+
IO::ConsoleMode.new(IO::Console::Windows.console_mode(self))
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def console_mode=(mode)
|
|
165
|
+
IO::Console::Windows.set_console_mode(self, mode.__send__(:to_i))
|
|
166
|
+
mode
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def input_pending?
|
|
170
|
+
windows = IO::Console::Windows
|
|
171
|
+
return windows::Kbhit.call != 0 if windows::GetConsoleMode.call(windows.handle(self), "\0" * 4) != 0
|
|
172
|
+
respond_to?(:wait_readable) && !!wait_readable(0)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def console_input_events(max_events = 1, timeout: nil)
|
|
176
|
+
raise ArgumentError, 'max_events must be positive' unless max_events > 0
|
|
177
|
+
raise ArgumentError, 'time interval must not be negative' if timeout && timeout < 0
|
|
178
|
+
|
|
179
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout if timeout
|
|
180
|
+
loop do
|
|
181
|
+
wait = deadline && deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
182
|
+
return [] if wait && wait <= 0
|
|
183
|
+
milliseconds = wait ? [[(wait * 1000).ceil, 100].min, 0].max : 100
|
|
184
|
+
windows = IO::Console::Windows
|
|
185
|
+
result = windows::WaitForSingleObject.call(windows.handle(self), milliseconds)
|
|
186
|
+
break if result == windows::WAIT_OBJECT_0
|
|
187
|
+
return [] if result != windows::WAIT_TIMEOUT
|
|
188
|
+
return [] if deadline && Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
records = "\0" * 20 * max_events
|
|
192
|
+
count = "\0" * 4
|
|
193
|
+
windows = IO::Console::Windows
|
|
194
|
+
if windows::ReadConsoleInputW.call(windows.handle(self), records, max_events, count) == 0
|
|
195
|
+
raise SystemCallError, 'ReadConsoleInputW'
|
|
196
|
+
end
|
|
197
|
+
count.unpack1('L').times.map do |index|
|
|
198
|
+
record = records[index * 20, 20]
|
|
199
|
+
event_type = record.unpack1('S')
|
|
200
|
+
case event_type
|
|
201
|
+
when 1
|
|
202
|
+
key_down, repeat_count, virtual_key_code, virtual_scan_code,
|
|
203
|
+
unicode_char, control_key_state = record[4, 16].unpack('LS4L')
|
|
204
|
+
{
|
|
205
|
+
type: :key, key_down: key_down != 0, repeat_count: repeat_count,
|
|
206
|
+
virtual_key_code: virtual_key_code, virtual_scan_code: virtual_scan_code,
|
|
207
|
+
unicode_char: unicode_char, control_key_state: control_key_state,
|
|
208
|
+
}
|
|
209
|
+
when 2
|
|
210
|
+
x, y, button_state, control_key_state, event_flags = record[4, 16].unpack('s2L3')
|
|
211
|
+
{type: :mouse, position: [y, x], button_state: button_state,
|
|
212
|
+
control_key_state: control_key_state, event_flags: event_flags}
|
|
213
|
+
when 4
|
|
214
|
+
x, y = record[4, 4].unpack('s2')
|
|
215
|
+
{type: :window_buffer_size, size: [y, x]}
|
|
216
|
+
when 8
|
|
217
|
+
{type: :menu, command_id: record[4, 4].unpack1('L')}
|
|
218
|
+
when 16
|
|
219
|
+
{type: :focus, set_focus: record[4, 4].unpack1('L') != 0}
|
|
220
|
+
else
|
|
221
|
+
{type: event_type}
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def winsize
|
|
227
|
+
width, _, _, _, _, _, top, _, bottom = IO::Console::Windows.screen_buffer_info(self)
|
|
228
|
+
[bottom - top + 1, width]
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def cursor
|
|
232
|
+
_, _, x, y, _, _, top, = IO::Console::Windows.screen_buffer_info(self)
|
|
233
|
+
[y - top, x]
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def goto(row, column)
|
|
237
|
+
windows = IO::Console::Windows
|
|
238
|
+
_, _, _, _, _, _, top, = windows.screen_buffer_info(self)
|
|
239
|
+
position = windows.coordinate(column, row + top)
|
|
240
|
+
raise SystemCallError, 'SetConsoleCursorPosition' if windows::SetConsoleCursorPosition.call(windows.handle(self), position) == 0
|
|
241
|
+
self
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def goto_column(column)
|
|
245
|
+
row, = cursor
|
|
246
|
+
goto(row, column)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def erase_line(mode)
|
|
250
|
+
raise ArgumentError, 'invalid line erase mode' unless (0..2).cover?(mode)
|
|
251
|
+
windows = IO::Console::Windows
|
|
252
|
+
width, _, x, y, attributes, = windows.screen_buffer_info(self)
|
|
253
|
+
start = mode == 0 ? x : 0
|
|
254
|
+
length = mode == 0 ? width - x : mode == 1 ? x + 1 : width
|
|
255
|
+
position = windows.coordinate(start, y)
|
|
256
|
+
written = "\0" * 4
|
|
257
|
+
windows::FillConsoleOutputCharacter.call(windows.handle(self), 0x20, length, position, written)
|
|
258
|
+
windows::FillConsoleOutputAttribute.call(windows.handle(self), attributes, length, position, written)
|
|
259
|
+
self
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def clear_screen
|
|
263
|
+
windows = IO::Console::Windows
|
|
264
|
+
width, _, _, _, attributes, _, top, _, bottom = windows.screen_buffer_info(self)
|
|
265
|
+
length = width * (bottom - top + 1)
|
|
266
|
+
position = windows.coordinate(0, top)
|
|
267
|
+
written = "\0" * 4
|
|
268
|
+
windows::FillConsoleOutputCharacter.call(windows.handle(self), 0x20, length, position, written)
|
|
269
|
+
windows::FillConsoleOutputAttribute.call(windows.handle(self), attributes, length, position, written)
|
|
270
|
+
windows::SetConsoleCursorPosition.call(windows.handle(self), position)
|
|
271
|
+
self
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def hide_cursor
|
|
275
|
+
set_cursor_visibility(false)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def show_cursor
|
|
279
|
+
set_cursor_visibility(true)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
private def set_cursor_visibility(visible)
|
|
283
|
+
info = "\0" * 8
|
|
284
|
+
windows = IO::Console::Windows
|
|
285
|
+
handle = windows.handle(self)
|
|
286
|
+
raise SystemCallError, 'GetConsoleCursorInfo' if windows::GetConsoleCursorInfo.call(handle, info) == 0
|
|
287
|
+
size, = info.unpack('L2')
|
|
288
|
+
info = [size, visible ? 1 : 0].pack('L2')
|
|
289
|
+
raise SystemCallError, 'SetConsoleCursorInfo' if windows::SetConsoleCursorInfo.call(handle, info) == 0
|
|
290
|
+
self
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module IO::Console::Windows
|
|
2
|
+
VK_TAB = 0x09
|
|
3
|
+
VK_RETURN = 0x0d
|
|
4
|
+
VK_SHIFT = 0x10
|
|
5
|
+
VK_CONTROL = 0x11
|
|
6
|
+
VK_MENU = 0x12
|
|
7
|
+
VK_END = 0x23
|
|
8
|
+
VK_HOME = 0x24
|
|
9
|
+
VK_LEFT = 0x25
|
|
10
|
+
VK_UP = 0x26
|
|
11
|
+
VK_RIGHT = 0x27
|
|
12
|
+
VK_DOWN = 0x28
|
|
13
|
+
VK_DELETE = 0x2e
|
|
14
|
+
VK_DIVIDE = 0x6f
|
|
15
|
+
VK_LMENU = 0xa4
|
|
16
|
+
|
|
17
|
+
RIGHT_ALT_PRESSED = 0x0001
|
|
18
|
+
LEFT_ALT_PRESSED = 0x0002
|
|
19
|
+
RIGHT_CTRL_PRESSED = 0x0004
|
|
20
|
+
LEFT_CTRL_PRESSED = 0x0008
|
|
21
|
+
SHIFT_PRESSED = 0x0010
|
|
22
|
+
NUMLOCK_ON = 0x0020
|
|
23
|
+
SCROLLLOCK_ON = 0x0040
|
|
24
|
+
CAPSLOCK_ON = 0x0080
|
|
25
|
+
ENHANCED_KEY = 0x0100
|
|
26
|
+
end
|
data/lib/ffi/io/console.rb
CHANGED
|
@@ -24,51 +24,40 @@ require 'rbconfig'
|
|
|
24
24
|
require_relative 'console/version'
|
|
25
25
|
require_relative 'console/common'
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
# Attempt to load the native Linux and BSD console logic
|
|
34
|
-
require_relative 'console/native_console'
|
|
35
|
-
|
|
36
|
-
rescue Exception => ex
|
|
37
|
-
|
|
38
|
-
warn "failed to load native console support: #{ex}" if $VERBOSE
|
|
39
|
-
|
|
40
|
-
else
|
|
41
|
-
|
|
42
|
-
# Native ready.
|
|
43
|
-
ready = true
|
|
44
|
-
|
|
27
|
+
class IO
|
|
28
|
+
module Console
|
|
29
|
+
class Mode
|
|
30
|
+
VERSION = Console::VERSION
|
|
31
|
+
deprecate_constant :VERSION
|
|
32
|
+
end
|
|
45
33
|
end
|
|
46
34
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
ready = false
|
|
51
|
-
|
|
35
|
+
ConsoleMode = Console::Mode
|
|
36
|
+
deprecate_constant :ConsoleMode
|
|
52
37
|
end
|
|
53
38
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
end
|
|
39
|
+
libs = []
|
|
40
|
+
# If Linux or BSD, try to load the native version
|
|
41
|
+
case RbConfig::CONFIG['host_os'].downcase
|
|
42
|
+
when /darwin|openbsd|freebsd|netbsd/
|
|
43
|
+
libs << 'bsd' << 'stty'
|
|
44
|
+
when /linux/
|
|
45
|
+
libs << 'linux' << 'stty'
|
|
46
|
+
when /mswin|win32|ming/i
|
|
47
|
+
require_relative 'console/windows_constants'
|
|
48
|
+
require_relative 'console/windows_console'
|
|
49
|
+
return
|
|
50
|
+
else
|
|
51
|
+
libs << 'stty'
|
|
68
52
|
end
|
|
69
53
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
54
|
+
return if libs.any? do |lib|
|
|
55
|
+
require_relative "console/#{lib}_console"
|
|
56
|
+
rescue Exception => ex
|
|
57
|
+
warn "failed to load #{lib} console support: #{ex}" if $VERBOSE
|
|
58
|
+
else
|
|
59
|
+
true
|
|
74
60
|
end
|
|
61
|
+
|
|
62
|
+
# If still not ready, just use stubbed version
|
|
63
|
+
require_relative "console/stub_console"
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: io-console
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.1
|
|
5
5
|
platform: java
|
|
6
6
|
authors:
|
|
7
7
|
- Nobu Nakada
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 2026-08-08 00:00:00.000000000 Z
|
|
11
11
|
dependencies: []
|
|
12
12
|
description: add console capabilities to IO instances.
|
|
13
13
|
email: nobu@ruby-lang.org
|
|
@@ -27,6 +27,8 @@ files:
|
|
|
27
27
|
- lib/ffi/io/console/stty_console.rb
|
|
28
28
|
- lib/ffi/io/console/stub_console.rb
|
|
29
29
|
- lib/ffi/io/console/version.rb
|
|
30
|
+
- lib/ffi/io/console/windows_console.rb
|
|
31
|
+
- lib/ffi/io/console/windows_constants.rb
|
|
30
32
|
- lib/io/console/size.rb
|
|
31
33
|
homepage: https://github.com/ruby/io-console
|
|
32
34
|
licenses:
|