Search code examples
rubyeventmachine

EventMachine how to write keyboard handler reacting on keypress


I use EventMachine LineText2 protocol and I would like to fire receive_line method everytime I press a character on my keyboard and not just when I enter a new line. Is there a way to change that default behaviour?

class KeyboardHandler < EM::Connection
  include EM::Protocols::LineText2

  def initialize(q)
    @queue = q
  end

  def receive_line(data)
    @queue.push(data)
  end
end

EM.run {
  q = EM::Queue.new

  callback = Proc.new do |line|
    # puts on every keypress not on "\n"
    puts line
    q.pop(&callback)
  end
  q.pop(&callback)

  EM.open_keyboard(KeyboardHandler, q)
}

Solution

  • If you want to receive unbuffered input from the terminal, you should turn off canonical-mode on standard input. (I also turn off echo to make the screen easier to read.) Add this before your code calls #open_keyboard or within your handler initializer:

    require 'termios'
    # ...
    attributes = Termios.tcgetattr($stdin).dup
    attributes.lflag &= ~Termios::ECHO # Optional.
    attributes.lflag &= ~Termios::ICANON
    Termios::tcsetattr($stdin, Termios::TCSANOW, attributes)
    

    For example:

    require 'termios'
    require 'eventmachine'
    
    module UnbufferedKeyboardHandler
      def receive_data(buffer)
        puts ">>> #{buffer}"
      end
    end
    
    EM.run do
      attributes = Termios.tcgetattr($stdin).dup
      attributes.lflag &= ~Termios::ECHO
      attributes.lflag &= ~Termios::ICANON
      Termios::tcsetattr($stdin, Termios::TCSANOW, attributes)
    
      EM.open_keyboard(UnbufferedKeyboardHandler)
    end