diff --git a/lib/ruby/1.9/io/console.rb b/lib/ruby/1.9/io/console.rb index bcd9ffe172e..bffd9b435a7 100644 --- a/lib/ruby/1.9/io/console.rb +++ b/lib/ruby/1.9/io/console.rb @@ -19,301 +19,49 @@ # we don't actually disable echo and the password is shown...we will try to # do a better version of this in 1.7.1. -# attempt to call stty; if failure, fall back on stubbed version +require 'rbconfig' -if RbConfig::CONFIG['host_os'].downcase =~ /darwin|openbsd|freebsd|netbsd|linux/ - require 'java' +require 'io/console/common' - result = begin - if RbConfig::CONFIG['host_os'].downcase =~ /darwin|openbsd|freebsd|netbsd/ - require File.join(File.dirname(__FILE__), 'bsd_console') - - elsif RbConfig::CONFIG['host_os'].downcase =~ /linux/ - require File.join(File.dirname(__FILE__), 'linux_console') - - else - raise LoadError.new("no native io/console support") - end - - class IO - module LibC - begin - FD_FIELD = java.io.FileDescriptor.java_class.declared_field("fd") - FD_FIELD.accessible = true - - def self.fd(io) - FD_FIELD.value io.to_java.open_file_checked.main_stream_safe.descriptor.file_descriptor - end - rescue - def self.fd(io) - io.fileno - end - end - end - - def ttymode - termios = LibC::Termios.new - if LibC.tcgetattr(LibC.fd(self), termios) != 0 - raise SystemCallError.new("tcgetattr", FFI.errno) - end - - if block_given? - yield tmp = termios.dup - if LibC.tcsetattr(LibC.fd(self), LibC::TCSADRAIN, tmp) != 0 - raise SystemCallError.new("tcsetattr", FFI.errno) - end - end - termios - end - - def ttymode_yield(block, &setup) - begin - orig_termios = ttymode { |t| setup.call(t) } - block.call(self) - ensure - if orig_termios && LibC.tcsetattr(LibC.fd(self), LibC::TCSADRAIN, orig_termios) != 0 - raise SystemCallError.new("tcsetattr", FFI.errno) - end - end - end - - TTY_RAW = Proc.new do |t| - LibC.cfmakeraw(t) - t[:c_lflag] &= ~(LibC::ECHOE|LibC::ECHOK) - end - - def raw(*, &block) - ttymode_yield(block, &TTY_RAW) - end - - def raw!(*) - ttymode(&TTY_RAW) - end - - TTY_COOKED = Proc.new do |t| - t[:c_iflag] |= (LibC::BRKINT|LibC::ISTRIP|LibC::ICRNL|LibC::IXON) - t[:c_oflag] |= LibC::OPOST - t[:c_lflag] |= (LibC::ECHO|LibC::ECHOE|LibC::ECHOK|LibC::ECHONL|LibC::ICANON|LibC::ISIG|LibC::IEXTEN) - end - - def cooked(*, &block) - ttymode_yield(block, &TTY_COOKED) - end - - def cooked!(*) - ttymode(&TTY_COOKED) - end - - TTY_ECHO = LibC::ECHO | LibC::ECHOE | LibC::ECHOK | LibC::ECHONL - def echo=(echo) - ttymode do |t| - if echo - t[:c_lflag] |= TTY_ECHO - else - t[:c_lflag] &= ~TTY_ECHO - end - end - end - - def echo? - (ttymode[:c_lflag] & (LibC::ECHO | LibC::ECHONL)) != 0 - end - - def noecho(&block) - ttymode_yield(block) { |t| t[:c_lflag] &= ~(TTY_ECHO) } - end - - def getch(*) - raw do - getc - end - end - - def winsize - ws = LibC::Winsize.new - if LibC.ioctl(LibC.fd(self), LibC::TIOCGWINSZ, :pointer, ws.pointer) != 0 - raise SystemCallError.new("ioctl(TIOCGWINSZ)", FFI.errno) - end - [ ws[:ws_row], ws[:ws_col] ] - end - - def winsize=(size) - ws = LibC::Winsize.new - if LibC.ioctl(LibC.fd(self), LibC::TIOCGWINSZ, :pointer, ws.pointer) != 0 - raise SystemCallError.new("ioctl(TIOCGWINSZ)", FFI.errno) - end - - ws[:ws_row] = size[0] - ws[:ws_col] = size[1] - if LibC.ioctl(LibC.fd(self), LibC::TIOCSWINSZ, :pointer, ws.pointer) != 0 - raise SystemCallError.new("ioctl(TIOCSWINSZ)", FFI.errno) - end - end - - def iflush - raise SystemCallError.new("tcflush(TCIFLUSH)", FFI.errno) unless LibC.tcflush(LibC.fd(self), LibC::TCIFLUSH) == 0 - end - - def oflush - raise SystemCallError.new("tcflush(TCOFLUSH)", FFI.errno) unless LibC.tcflush(LibC.fd(self), LibC::TCOFLUSH) == 0 - end - - def ioflush - raise SystemCallError.new("tcflush(TCIOFLUSH)", FFI.errno) unless LibC.tcflush(LibC.fd(self), LibC::TCIOFLUSH) == 0 - end - end - true - rescue Exception => ex - warn "failed to load native console support: #{ex}" if $VERBOSE - begin - `stty 2> /dev/null` - $?.exitstatus != 0 - rescue Exception - nil - end - end +# If Windows, always use the stub version +if RbConfig::CONFIG['host_os'] =~ /(mswin)|(win32)|(ming)/ + require 'io/console/stub_console' else - result = begin - old_stderr = $stderr.dup - $stderr.reopen('/dev/null') - `stty -a` - $?.exitstatus != 0 - rescue Exception - nil - ensure - $stderr.reopen(old_stderr) - end -end - -if !result || RbConfig::CONFIG['host_os'] =~ /(mswin)|(win32)|(ming)/ - warn "io/console not supported; tty will not be manipulated" if $VERBOSE - # Windows version is always stubbed for now - class IO - def raw(*) - yield self - end - - def raw!(*) - end - - def cooked(*) - yield self - end - - def cooked!(*) - end - - def getch(*) - getc - end - - def echo=(echo) - end - - def echo? - true - end - - def noecho - yield self - end + # If Linux or BSD, try to load the native version + if RbConfig::CONFIG['host_os'].downcase =~ /darwin|openbsd|freebsd|netbsd|linux/ + begin - def winsize - [25, 80] - end + # Attempt to load the native Linux and BSD console logic + # require 'io/console/native_console' + # ready = true - def winsize=(size) - end + rescue Exception => ex - def iflush - end + warn "failed to load native console support: #{ex}" if $VERBOSE + ready = false - def oflush - end - - def ioflush end end -elsif !IO.method_defined?:ttymode - warn "io/console on JRuby shells out to stty for most operations" - - # Non-Windows assumes stty command is available - class IO - if RbConfig::CONFIG['host_os'].downcase =~ /linux/ && File.exists?("/proc/#{Process.pid}/fd") - def stty(*args) - `stty #{args.join(' ')} < /proc/#{Process.pid}/fd/#{fileno}` - end - else - def stty(*args) - `stty #{args.join(' ')}` - end - end - - def raw(*) - saved = stty('-g') - stty('raw') - yield self - ensure - stty(saved) - end - - def raw!(*) - stty('raw') - end - - def cooked(*) - saved = stty('-g') - stty('-raw') - yield self - ensure - stty(saved) - end - - def cooked!(*) - stty('-raw') - end - - def getch(*) - getc - end - def echo=(echo) - stty(echo ? 'echo' : '-echo') - end - - def echo? - (stty('-a') =~ / -echo /) ? false : true - end - - def noecho - saved = stty('-g') - stty('-echo') - yield self - ensure - stty(saved) - end + # Native failed, try to use stty + if !ready + begin - # Not all systems return same format of stty -a output - IEEE_STD_1003_2 = '(?\d+) rows; (?\d+) columns' - UBUNTU = 'rows (?\d+); columns (?\d+)' + require 'io/console/stty_console' + ready = true - def winsize - match = stty('-a').match(/#{IEEE_STD_1003_2}|#{UBUNTU}/) - [match[:rows].to_i, match[:columns].to_i] - end + rescue Exception - def winsize=(size) - stty("rows #{size[0]} cols #{size[1]}") - end + warn "failed to load stty console support: #{ex}" if $VERBOSE + ready = false - def iflush - end - - def oflush end + end - def ioflush - end + # If still not ready, just use stubbed version + if !ready + require 'io/console/stub_console' end + end diff --git a/lib/ruby/1.9/io/bsd_console.rb b/lib/ruby/1.9/io/console/bsd_console.rb similarity index 100% rename from lib/ruby/1.9/io/bsd_console.rb rename to lib/ruby/1.9/io/console/bsd_console.rb diff --git a/lib/ruby/1.9/io/console/common.rb b/lib/ruby/1.9/io/console/common.rb new file mode 100644 index 00000000000..10f7fc03961 --- /dev/null +++ b/lib/ruby/1.9/io/console/common.rb @@ -0,0 +1,35 @@ +# Methods common to all backend impls +class IO + def getch(*) + raw do + getc + end + end + + def getpass(prompt = nil) + wio = self == $stdin ? $stderr : self + wio.write(prompt) if prompt + begin + str = nil + noecho do + str = gets + end + ensure + puts($/) + end + str.chomp + end + + module GenericReadable + def getch(*) + getc + end + + def getpass(prompt = nil) + write(prompt) if prompt + str = gets.chomp + puts($/) + str + end + end +end \ No newline at end of file diff --git a/lib/ruby/1.9/io/linux_console.rb b/lib/ruby/1.9/io/console/linux_console.rb similarity index 100% rename from lib/ruby/1.9/io/linux_console.rb rename to lib/ruby/1.9/io/console/linux_console.rb diff --git a/lib/ruby/1.9/io/console/native_console.rb b/lib/ruby/1.9/io/console/native_console.rb new file mode 100644 index 00000000000..8fc3cf4cc43 --- /dev/null +++ b/lib/ruby/1.9/io/console/native_console.rb @@ -0,0 +1,153 @@ +# Load appropriate native bits for BSD or Linux +case RbConfig::CONFIG['host_os'].downcase +when /darwin|openbsd|freebsd|netbsd/ + require 'io/console/bsd_console' +when /linux/ + require 'io/console/linux_console' +else + raise LoadError.new("no native io/console support") +end + +# Common logic that uses native calls for console +class IO + def ttymode + termios = LibC::Termios.new + if LibC.tcgetattr(self.fileno, termios) != 0 + raise SystemCallError.new("tcgetattr", FFI.errno) + end + + if block_given? + yield tmp = termios.dup + if LibC.tcsetattr(self.fileno, LibC::TCSADRAIN, tmp) != 0 + raise SystemCallError.new("tcsetattr", FFI.errno) + end + end + termios + end + private :ttymode + + def ttymode_yield(block, &setup) + begin + orig_termios = ttymode { |t| setup.call(t) } + block.call(self) + ensure + if orig_termios && LibC.tcsetattr(self.fileno, LibC::TCSADRAIN, orig_termios) != 0 + raise SystemCallError.new("tcsetattr", FFI.errno) + end + end + end + private :ttymode_yield + + TTY_RAW = Proc.new do |t| + LibC.cfmakeraw(t) + t[:c_lflag] &= ~(LibC::ECHOE|LibC::ECHOK) + end + + def raw(*, &block) + ttymode_yield(block, &TTY_RAW) + end + + def raw!(*) + ttymode(&TTY_RAW) + end + + TTY_COOKED = Proc.new do |t| + t[:c_iflag] |= (LibC::BRKINT|LibC::ISTRIP|LibC::ICRNL|LibC::IXON) + t[:c_oflag] |= LibC::OPOST + t[:c_lflag] |= (LibC::ECHO|LibC::ECHOE|LibC::ECHOK|LibC::ECHONL|LibC::ICANON|LibC::ISIG|LibC::IEXTEN) + end + + def cooked(*, &block) + ttymode_yield(block, &TTY_COOKED) + end + + def cooked!(*) + ttymode(&TTY_COOKED) + end + + TTY_ECHO = LibC::ECHO | LibC::ECHOE | LibC::ECHOK | LibC::ECHONL + def echo=(echo) + ttymode do |t| + if echo + t[:c_lflag] |= TTY_ECHO + else + t[:c_lflag] &= ~TTY_ECHO + end + end + end + + def echo? + (ttymode[:c_lflag] & (LibC::ECHO | LibC::ECHONL)) != 0 + end + + def noecho(&block) + ttymode_yield(block) { |t| t[:c_lflag] &= ~(TTY_ECHO) } + end + + def winsize + ws = LibC::Winsize.new + if LibC.ioctl(self.fileno, LibC::TIOCGWINSZ, :pointer, ws.pointer) != 0 + raise SystemCallError.new("ioctl(TIOCGWINSZ)", FFI.errno) + end + [ ws[:ws_row], ws[:ws_col] ] + end + + def winsize=(size) + ws = LibC::Winsize.new + if LibC.ioctl(self.fileno, LibC::TIOCGWINSZ, :pointer, ws.pointer) != 0 + raise SystemCallError.new("ioctl(TIOCGWINSZ)", FFI.errno) + end + + ws[:ws_row] = size[0] + ws[:ws_col] = size[1] + if LibC.ioctl(self.fileno, LibC::TIOCSWINSZ, :pointer, ws.pointer) != 0 + raise SystemCallError.new("ioctl(TIOCSWINSZ)", FFI.errno) + end + end + + def iflush + raise SystemCallError.new("tcflush(TCIFLUSH)", FFI.errno) unless LibC.tcflush(self.fileno, LibC::TCIFLUSH) == 0 + end + + def oflush + raise SystemCallError.new("tcflush(TCOFLUSH)", FFI.errno) unless LibC.tcflush(self.fileno, LibC::TCOFLUSH) == 0 + end + + def ioflush + raise SystemCallError.new("tcflush(TCIOFLUSH)", FFI.errno) unless LibC.tcflush(self.fileno, LibC::TCIOFLUSH) == 0 + end + + # TODO: Windows version uses "conin$" and "conout$" instead of /dev/tty + def self.console(sym = nil, *args) + raise TypeError, "expected Symbol, got #{sym.class}" unless sym.nil? || sym.kind_of?(Symbol) + + # klass = self == IO ? File : self + if defined?(@console) # using ivar instead of hidden const as in MRI + con = @console + # MRI checks IO internals : (!RB_TYPE_P(con, T_FILE) || (!(fptr = RFILE(con)->fptr) || GetReadFD(fptr) == -1)) + if !con.kind_of?(File) || (con.kind_of?(IO) && (con.closed? || !FileTest.readable?(con))) + remove_instance_variable :@console + con = nil + end + end + + if sym + if sym == :close + if con + con.close + remove_instance_variable :@console if defined?(@console) + end + return nil + end + end + + if !con && $stdin.tty? + con = File.open('/dev/tty', 'r+') + con.sync = true + @console = con + end + + return con.send(sym, *args) if sym + return con + end +end diff --git a/lib/ruby/1.9/io/console/stty_console.rb b/lib/ruby/1.9/io/console/stty_console.rb new file mode 100644 index 00000000000..1832a9a85a2 --- /dev/null +++ b/lib/ruby/1.9/io/console/stty_console.rb @@ -0,0 +1,82 @@ +# attempt to call stty; if failure, raise error +`stty 2> /dev/null` +if $?.exitstatus != 0 + raise "stty command returned nonzero exit status" +end + +warn "io/console on JRuby shells out to stty for most operations" + +# Non-Windows assumes stty command is available +class IO + if RbConfig::CONFIG['host_os'].downcase =~ /linux/ && File.exists?("/proc/#{Process.pid}/fd") + def stty(*args) + `stty #{args.join(' ')} < /proc/#{Process.pid}/fd/#{fileno}` + end + else + def stty(*args) + `stty #{args.join(' ')}` + end + end + + def raw(*) + saved = stty('-g') + stty('raw') + yield self + ensure + stty(saved) + end + + def raw!(*) + stty('raw') + end + + def cooked(*) + saved = stty('-g') + stty('-raw') + yield self + ensure + stty(saved) + end + + def cooked!(*) + stty('-raw') + end + + def echo=(echo) + stty(echo ? 'echo' : '-echo') + end + + def echo? + (stty('-a') =~ / -echo /) ? false : true + end + + def noecho + saved = stty('-g') + stty('-echo') + yield self + ensure + stty(saved) + end + + # Not all systems return same format of stty -a output + IEEE_STD_1003_2 = '(?\d+) rows; (?\d+) columns' + UBUNTU = 'rows (?\d+); columns (?\d+)' + + def winsize + match = stty('-a').match(/#{IEEE_STD_1003_2}|#{UBUNTU}/) + [match[:rows].to_i, match[:columns].to_i] + end + + def winsize=(size) + stty("rows #{size[0]} cols #{size[1]}") + end + + def iflush + end + + def oflush + end + + def ioflush + end +end \ No newline at end of file diff --git a/lib/ruby/1.9/io/console/stub_console.rb b/lib/ruby/1.9/io/console/stub_console.rb new file mode 100644 index 00000000000..2ebe36ca8cd --- /dev/null +++ b/lib/ruby/1.9/io/console/stub_console.rb @@ -0,0 +1,45 @@ +warn "io/console not supported; tty will not be manipulated" if $VERBOSE + +# Windows version is always stubbed for now +class IO + def raw(*) + yield self + end + + def raw!(*) + end + + def cooked(*) + yield self + end + + def cooked!(*) + end + + def echo=(echo) + end + + def echo? + true + end + + def noecho + yield self + end + + def winsize + [25, 80] + end + + def winsize=(size) + end + + def iflush + end + + def oflush + end + + def ioflush + end +end \ No newline at end of file