Search code examples
rubyterminalcommand-line-interfacemouseansi-escape

Capture Mouse Events in the Terminal using Ruby


Here's some code I've been experimenting with:

print "\e[?1003h"

begin
  loop do
    begin
      input = ARGF.read_nonblock(1024)
      puts "input: #{input.inspect}"
    rescue IO::EAGAINWaitReadable
    end
  end
ensure
  print "\e[?1003l"
end

When I run this code, I can see content like this being printed to the terminal, but none of it indicates it's being sent to ARGF. I've tried STDIN as well. My read_nonblock calls always raise IO::EAGAINWaitReadable, which seems to be the expected behavior (see spec).

^[[MCn@^[[MCn?^[[MCn>^[[MCm>^[[MCm=^[[MCk;^[[MCk:^[[MCj:^[[MCi:^[[MCh:^[[MCg:^[[MCe:^[[MCd:^[[MCc:^[[MCb;^[[MCb<^[[MCc<^[[MCd<^[[MCe<^[[MCf<^[[MCg<^[[MCg;^[[MCf;^[[MCf:^[[MCe:^[[MCd:^[[MCd;^[[MCd<^[[MCe<^[[MCf<^[[MCg<^[[MCg;^[[MCg<^[[MCg;^[[MCf;^[[MCf:^[[MCe:^[[MCd:^[[MCd;^[[MCe;^[[MCe<^[[MCe;^[[MCf;^[[MCe;^[[MCe:^[[MCe;^[[MCf;^[[MCe;^[[MCd;^[[MCd<^[[MCe<^[[MCe=^[[MCf=^[[MCf<^[[MCe<^[[MCe;^[[MCd;^[[MCc;^[[MCc<^[[MCd<^[[MCe<^[[MCe;^[[MCe<^[[MCf<^[[MCf;^[[MCe;^[[MCd;^[[MCd<^[[MCe<^[[MCe=^[[MCf=^[[MCg=^[[MCh=^[[MCi>^[[MCj>^[[MCk>^[[MCl>^[[MCn?^[[MCpA^[[MCqB^[[MCrC^[[MCrD^[[MCsD^[[MCsE^[[MCtF^[[MCsG^[[MCrG^[[MCqG^[[MCpG^[[MCpH^[[MCmH^[[MCmG^[[MClG^[[MCgI^[[MCfI^[[MCeI^[[MC]I^[[MC\I^[[MC[I

Solution

  • If you want to do your own processing, you have to capture the characters in raw mode.

    This should work: (I've tried to keep your code structure)

    require 'io/console'
    
    at_exit { print "\e[?1003l" }
    
    print "\e[?1003h"
    
    STDIN.raw(intr: true) do # raw mode but allow ctrl-c
      loop do
        begin
          input = STDIN.read_nonblock(1024)
          p input: input
        rescue IO::EAGAINWaitReadable
          IO.select([STDIN])
          retry
        end
      end
    end
    

    Output: (while moving the mouse)

    $ ruby mouse.rb
    {:input=>"\e[MCYB"}
    {:input=>"\e[MCZB"}
    {:input=>"\e[MC[B"}
    {:input=>"\e[MC\\A"}
    {:input=>"\e[MC]A"}
    {:input=>"\e[MC]B"}
    {:input=>"\e[MC\\B"}
    

    Of course, you still have to actually process that mouse input in a meaningful way.