123456789_123456789_123456789_123456789_123456789_

Class: RubyInstaller::Runtime::ConsoleUi

Relationships & Source Files
Namespace Children
Classes:
Inherits: Object
Defined in: lib/ruby_installer/runtime/console_ui.rb

Constant Summary

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Constructor Details

.newConsoleUi

[ GitHub ]

  
# File 'lib/ruby_installer/runtime/console_ui.rb', line 185

def initialize
  @GetStdHandle = Win32API.new('kernel32', 'GetStdHandle', ['L'], 'L')
  @GetConsoleMode = Win32API.new('kernel32', 'GetConsoleMode', ['L', 'P'], 'L')
  @SetConsoleMode = Win32API.new('kernel32', 'SetConsoleMode', ['L', 'L'], 'L')
  @ReadConsoleInputW = Win32API.new('kernel32', 'ReadConsoleInputW', ['L', 'P', 'L', 'P'], 'L')
  @GetConsoleScreenBufferInfo = Win32API.new('kernel32', 'GetConsoleScreenBufferInfo', ['L', 'P'], 'L')

  @hConsoleHandle = @GetStdHandle.call(STD_INPUT_HANDLE)
  @hConsoleOutHandle = @GetStdHandle.call(STD_OUTPUT_HANDLE)

  @mouse_state = 0
  @old_winsize = IO.console.winsize
  set_consolemode

  at_exit do
    unset_consolemode
  end
end

Instance Attribute Details

#widget (rw)

[ GitHub ]

  
# File 'lib/ruby_installer/runtime/console_ui.rb', line 183

attr_accessor :widget

#windows_terminal?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/ruby_installer/runtime/console_ui.rb', line 329

private def windows_terminal?
  !!ENV["WT_SESSION"]
end

#winsize_changed?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/ruby_installer/runtime/console_ui.rb', line 269

private def winsize_changed?
  con = IO.console
  new_size = con.winsize
  if @old_winsize != new_size
    @old_winsize = new_size
    true
  else
    false
  end
end

Instance Method Details

#call_with_console_handle(win32func, *args) (private)

Calling Win32API with console handle is reported to fail after executing some external command. We need to refresh console handle and retry the call again.

[ GitHub ]

  
# File 'lib/ruby_installer/runtime/console_ui.rb', line 229

private def call_with_console_handle(win32func, *args)
  val = win32func.call(@hConsoleHandle, *args)
  return val if val != 0

  @hConsoleHandle = @GetStdHandle.call(STD_INPUT_HANDLE)
  win32func.call(@hConsoleHandle, *args)
end

#clear_screen

[ GitHub ]

  
# File 'lib/ruby_installer/runtime/console_ui.rb', line 204

def clear_screen
  IO.console.write "\e[H" "\e[2J"
end

#get_console_screen_buffer_info

[ GitHub ]

  
# File 'lib/ruby_installer/runtime/console_ui.rb', line 247

def get_console_screen_buffer_info
  # CONSOLE_SCREEN_BUFFER_INFO
  # [ 0,2] dwSize.X
  # [ 2,2] dwSize.Y
  # [ 4,2] dwCursorPositions.X
  # [ 6,2] dwCursorPositions.Y
  # [ 8,2] wAttributes
  # [10,2] srWindow.Left
  # [12,2] srWindow.Top
  # [14,2] srWindow.Right
  # [16,2] srWindow.Bottom
  # [18,2] dwMaximumWindowSize.X
  # [20,2] dwMaximumWindowSize.Y
  csbi = 0.chr * 22
  if @GetConsoleScreenBufferInfo.call(@hConsoleOutHandle, csbi) != 0
    # returns [width, height, x, y, attributes, left, top, right, bottom]
    csbi.unpack("s9")
  else
    return nil
  end
end

#getconsolemode (private)

[ GitHub ]

  
# File 'lib/ruby_installer/runtime/console_ui.rb', line 237

private def getconsolemode
  mode = +"\0\0\0\0"
  call_with_console_handle(@GetConsoleMode, mode)
  mode.unpack1('L')
end

#handle_key_input(str) (private)

[ GitHub ]

  
# File 'lib/ruby_installer/runtime/console_ui.rb', line 333

private def handle_key_input(str)
  case str
  when "\e[D", "\xE0K".b # cursor left
    widget.cursor(:left)
  when "\e[A", "\xE0H".b # cursor up
    widget.cursor(:up)
  when "\e[C", "\xE0M".b # cursor right
    widget.cursor(:right)
  when "\e[B", "\xE0P".b # cursor down
    widget.cursor(:down)
  when "\r" # enter
    unset_consolemode do
      widget.select
    end
  when /\A\e\x5b<0;(\d+);(\d+)m\z/ # Mouse left button up
    if widget.click($1.to_i - 1, $2.to_i - 2)
      widget.repaint
      unset_consolemode do
        widget.select
      end
    end
  when /\A\e\x5b<\d+;(\d+);(\d+)[Mm]\z/ # other mouse events
    return # no repaint
  end
  widget.repaint
end

#main_loop (private)

[ GitHub ]

  
# File 'lib/ruby_installer/runtime/console_ui.rb', line 360

private def main_loop
  str = +""
  console_buffer = StringIO.new
  loop do
    if windows_terminal?
      c = IO.console

      rs, = IO.select([c], [], [], 0.5)
      if rs
        char = c.read(1)
        break unless char
      else
        # timeout -> check windows size change
        widget.repaint if winsize_changed?
      end
    else
      if console_buffer.eof?
        input = read_input_event
        if input == :winsize_changed
          widget.repaint if winsize_changed?
        elsif input
          console_buffer = StringIO.new(input)
        end
      end
      char = console_buffer.read(1)
    end
    next unless char
    str << char

    next if !str.valid_encoding? ||
          str == "\e" ||
          str == "\e[" ||
          str == "\xE0" ||
          str.match(/\A\e\x5b<[0-9;]*\z/)

    handle_key_input(str)
    str = +""
  end
end

#read_input_event

[ GitHub ]

  
# File 'lib/ruby_installer/runtime/console_ui.rb', line 280

def read_input_event
  # Wait for reception of at least one event
  input_records = 0.chr * 20 * 1
  read_event = 0.chr * 4

  if @ReadConsoleInputW.(@hConsoleHandle, input_records, 1, read_event) != 0
    read_events = read_event.unpack1('L')
    0.upto(read_events-1) do |idx|
      input_record = input_records[idx * 20, 20]
      event = input_record[0, 2].unpack1('s*')
      case event
      when KEY_EVENT
        key_down = input_record[4, 4].unpack1('l*')
        repeat_count = input_record[8, 2].unpack1('s*')
        virtual_key_code = input_record[10, 2].unpack1('s*')
        virtual_scan_code = input_record[12, 2].unpack1('s*')
        char_code = input_record[14, 2].unpack1('S*')
        control_key_state = input_record[16, 2].unpack1('S*')
        is_key_down = key_down.zero? ? false : true
        if is_key_down
          # p [repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state]

          return char_code.chr
        end
      when MOUSE_EVENT
        click_x, click_y, state = input_record[4, 8].unpack("ssL")
        if @mouse_state != state
          # click state changed
          @mouse_state = state
          csbi = get_console_screen_buffer_info || raise("error at GetConsoleScreenBufferInfo")
          click_y -= csbi[6]
          # p mouse: [click_x, click_y, state]

          if state == 1
            # mouse button down
            return "\e\x5b<0;#{click_x};#{click_y}M"
          else
            # mouse button up
            return "\e\x5b<0;#{click_x};#{click_y}m"
          end
        end
      when WINDOW_BUFFER_SIZE_EVENT
        return :winsize_changed
      end
    end
  end
  false
end

#run!

[ GitHub ]

  
# File 'lib/ruby_installer/runtime/console_ui.rb', line 400

def run!
  widget.repaint
  main_loop
end

#set_consolemode

[ GitHub ]

  
# File 'lib/ruby_installer/runtime/console_ui.rb', line 208

def set_consolemode
  @base_console_input_mode = getconsolemode
  setconsolemode(ENABLE_PROCESSED_INPUT | ENABLE_MOUSE_INPUT | ENABLE_EXTENDED_FLAGS | ENABLE_VIRTUAL_TERMINAL_INPUT)
end

#setconsolemode(mode) (private)

[ GitHub ]

  
# File 'lib/ruby_installer/runtime/console_ui.rb', line 243

private def setconsolemode(mode)
  call_with_console_handle(@SetConsoleMode, mode)
end

#unset_consolemode

[ GitHub ]

  
# File 'lib/ruby_installer/runtime/console_ui.rb', line 213

def unset_consolemode
  if @base_console_input_mode
    setconsolemode(@base_console_input_mode | ENABLE_EXTENDED_FLAGS)
    @base_console_input_mode = nil
    if block_given?
      begin
        yield
      ensure
        set_consolemode
      end
    end
  end
end