diff --git a/Rakefile b/Rakefile index 4727c59..b594fc9 100644 --- a/Rakefile +++ b/Rakefile @@ -25,11 +25,13 @@ task ffi_version_file => "#{name.tr('/', '-')}.gemspec" do |t| end task :build => ffi_version_file +task :test => ffi_version_file if RUBY_ENGINE == "jruby" Rake::TestTask.new(:test) do |t| if extask t.libs = [extask.lib_dir.chomp("/"+File.dirname(name))] end + t.libs.unshift "lib/ffi" if RUBY_ENGINE == "jruby" t.libs << "test/lib" t.ruby_opts << "-rhelper" t.options = "--ignore-name=TestIO_Console#test_bad_keyword" if RUBY_ENGINE == "jruby" diff --git a/lib/ffi/io/console/bsd_console.rb b/lib/ffi/io/console/bsd_console.rb index 2785ef4..79da09e 100644 --- a/lib/ffi/io/console/bsd_console.rb +++ b/lib/ffi/io/console/bsd_console.rb @@ -6,8 +6,6 @@ raise LoadError.new("native console on MacOS only supported on #{tested_platforms.join(', ')}") end -require_relative 'native_console' - module IO::LibC extend FFI::Library ffi_lib FFI::Library::LIBC @@ -172,3 +170,5 @@ class Winsize < FFI::Struct attach_function :tcflush, [ :int, :int ], :int attach_function :ioctl, [ :int, :ulong, :varargs ], :int end + +require_relative 'native_console' diff --git a/lib/ffi/io/console/linux_console.rb b/lib/ffi/io/console/linux_console.rb index 33bd672..c2f30da 100644 --- a/lib/ffi/io/console/linux_console.rb +++ b/lib/ffi/io/console/linux_console.rb @@ -6,8 +6,6 @@ warn "native console only tested on #{tested_platforms.join(', ')}" end -require_relative 'native_console' - module IO::LibC extend FFI::Library ffi_lib FFI::Library::LIBC @@ -204,3 +202,5 @@ class Winsize < FFI::Struct attach_function :tcflush, [ :int, :int ], :int attach_function :ioctl, [ :int, :ulong, :varargs ], :int end + +require_relative 'native_console' diff --git a/lib/ffi/io/console/native_console.rb b/lib/ffi/io/console/native_console.rb index f0390bf..09aacb6 100644 --- a/lib/ffi/io/console/native_console.rb +++ b/lib/ffi/io/console/native_console.rb @@ -8,21 +8,26 @@ def ttymode if block_given? yield tmp = termios.dup - if LibC.tcsetattr(self.fileno, LibC::TCSANOW, tmp) != 0 - raise SystemCallError.new(respond_to?(:path) ? path : "tcsetattr(TCSANOW)", FFI.errno) - end + ttymode_set(tmp) end termios end private :ttymode + def ttymode_set(termios) + if LibC.tcsetattr(self.fileno, LibC::TCSANOW, termios) != 0 + raise SystemCallError.new(respond_to?(:path) ? path : "tcsetattr(TCSANOW)", FFI.errno) + end + end + private :ttymode_set + def ttymode_yield(block, **opts, &setup) begin orig_termios = ttymode { |t| setup.call(t, **opts) } block.call(self) ensure - if orig_termios && LibC.tcsetattr(self.fileno, LibC::TCSANOW, orig_termios) != 0 - raise SystemCallError.new(respond_to?(:path) ? path : "tcsetattr(TCSANOW)", FFI.errno) + if orig_termios + ttymode_set(orig_termios) end end end @@ -83,6 +88,46 @@ def noecho(&block) ttymode_yield(block) { |t| t[:c_lflag] &= ~(TTY_ECHO) } end + class ConsoleMode + attr_reader :termios + + def initialize(t) + @termios = t + end + + def initialize_copy(m) + @termios = m.termios.dup + end + + def echo=(echo) + if echo + @termios[:c_lflag] |= TTY_ECHO + else + @termios[:c_lflag] &= ~TTY_ECHO + end + end + + def raw!(min: 1, time: nil, intr: nil) + TTY_RAW[@termios, min:, time:, intr:] + self + end + + def raw(min: 1, time: nil, intr: nil) + new_mode = dup + TTY_RAW[new_mode.termios, min:, time:, intr:] + new_mode + end + end + + def console_mode + ConsoleMode.new(ttymode) + end + + def console_mode=(mode) + ttymode_set(mode.termios) + mode + end + def winsize ws = LibC::Winsize.new if LibC.ioctl(self.fileno, LibC::TIOCGWINSZ, :pointer, ws.pointer) != 0 diff --git a/test/io/console/test_io_console.rb b/test/io/console/test_io_console.rb index 1ce1de0..83cb4e7 100644 --- a/test/io/console/test_io_console.rb +++ b/test/io/console/test_io_console.rb @@ -270,6 +270,29 @@ def test_setecho2 } end + def test_console_mode + helper {|m, s| + begin + original = s.console_mode + assert_kind_of(IO::ConsoleMode, original) + + noecho = original.dup + noecho.echo = false + assert_same(noecho, s.send(:console_mode=, noecho)) + assert_not_predicate(s, :echo?) + + raw = original.raw + assert_not_same(original, raw) + assert_same(raw, raw.raw!) + assert_same(raw, s.send(:console_mode=, raw)) + s.print "raw\n" + assert_equal("raw\n", m.gets) + ensure + s.console_mode = original if original + end + } + end + def test_tty_on_pty pend "not supported" unless TTY_ENHANCED