The TerminalFilter class implements a subset of ANSI/VT100 protocol.
Construct filter
pstack
The ProtocolStack associated with this filter
# File lib/network/protocol/terminalfilter.rb, line 31 def initialize(pstack) super(pstack) @mode = :ground # Parse mode :ground, :escape @csi = "" end
Handle server-side echo
# File lib/network/protocol/terminalfilter.rb, line 385 def echo(ch) if @pstack.echo_on if @pstack.hide_on && ch[0] != CR @pstack.conn.sock.send('*',0) else @pstack.conn.sock.send(ch,0) end end end
# File lib/network/protocol/terminalfilter.rb, line 395 def execute(b) case b when ENQ, SO, SI, DC1, DC3 # not handled # ENQ Transmit ANSWERBACK message # SO Switch to G1 character set # SI Switch to G0 character set # DC1 Causes terminal to resume transmission (XON). # DC3 Causes terminal to stop transmitting all codes except XOFF and XON (XOFF). "" when VT, FF "[UP 1]" when TAB log.debug("(#{@pstack.conn.object_id}) TAB found") "[TAB]" when BEL "[BELL]" else "" end end
The #filter_in method filters out VTxx terminal data and inserts format strings into the input stream.
str
The string to be processed
return
The filtered data
# File lib/network/protocol/terminalfilter.rb, line 47 def filter_in(str) buf = "" str.each_byte do |b| case mode? when :ground case b when 0x20..0x7e buf << b.chr when ESC log.debug("(#{@pstack.conn.object_id}) ESC found") @collect = "" set_mode :escape # These cause immediate execution no matter what mode when ENQ, BEL, BS, TAB, VT, FF, SO, SI, DC1, DC3, CAN, SUB, DEL case b when BS, DEL log.debug("(#{@pstack.conn.object_id}) BS, DEL found") # slice local buffer or connection buffer buf.slice!(-1) || @pstack.conn.inbuffer.slice!(-1) when CAN, SUB @collect = "" set_mode :ground else buf << execute(b) end else buf << b.chr end when :escape case b when [[ log.debug("(#{@pstack.conn.object_id}) CSI sequence found") set_mode :csi when ]] log.debug("(#{@pstack.conn.object_id}) OSC/XTERM sequence found") set_mode :xterm when PP log.debug("(#{@pstack.conn.object_id}) DCS sequence found") set_mode :dcs when OO log.debug("(#{@pstack.conn.object_id}) SS3 sequence found") set_mode :ss3 when XX, ^^, __ log.debug("(#{@pstack.conn.object_id}) SOS/PM/APC sequence found") set_mode :sospmapc when DD buf << "[SCROLLDOWN]" set_mode :ground when MM buf << "[SCROLLUP]" set_mode :ground # VT52 when AA buf << "[UP 1]" set_mode :ground when BB buf << "[DOWN 1]" set_mode :ground when CC buf << "[RIGHT 1]" set_mode :ground when DD buf << "[LEFT 1]" set_mode :ground # /VT52 # when ?H # Set tab at current position - ignored # when ?E # Next line - like CRLF? # when ?7 # Save cursor and attributes SCURA # when ?8 # Restore cursor and attributes RCURA # when ?c # reset device # These cause immediate execution no matter what mode when ENQ, BEL, BS, TAB, VT, FF, SO, SI, DC1, DC3, CAN, SUB, DEL case b when BS, DEL log.debug("(#{@pstack.conn.object_id}) BS, DEL found") # slice local buffer or connection buffer buf.slice!(-1) || @pstack.conn.inbuffer.slice!(-1) when CAN, SUB @collect = "" set_mode :ground else buf << execute(b) end when 0x20..0x2F # " !"#$%&'()*+,-./" @collect << b.chr set_mode :escint else # These should all be immediately dispatched and sent to ground mode # when "0123456789:;<=>?@ABCDEFGHIJKLMNO" 0x30..0x4F # when "QRSTUVW" 0x51..0x57 # when "YZ" 0x59..0x5A # when "\\" 0x5C # when "`abcdefghijklmnopqrstuvwxyz{|}~" 0x60..0x7e set_mode :ground end when :escint # case b # when ?( # Set default font # when ?) # Set alternate font # both ( and ) may be followed by A,B,0,1,2 !! # end set_mode :ground when :dcs # terminated by ST or ESC \ set_mode :ground when :xterm set_mode :ground when :sospmapc set_mode :ground when :csi case b when AA buf << "[UP #{@collect.to_i == 0 ? 1 : @collect.to_i}]" set_mode :ground when BB buf << "[DOWN #{@collect.to_i == 0 ? 1 : @collect.to_i}]" set_mode :ground when CC buf << "[RIGHT #{@collect.to_i == 0 ? 1 : @collect.to_i}]" set_mode :ground when DD buf << "[LEFT #{@collect.to_i == 0 ? 1 : @collect.to_i}]" set_mode :ground when HH, ff # set cursor position \e[H or \e[<row>;<col>H a = @collect.split(";") a = ["1","1"] if a.empty? buf << "[HOME #{a[0]},#{a[1]}]" set_mode :ground when RR # report cursor pos a = @collect.split(";") a = ["1","1"] if a.empty? buf << "[CURSOR #{a[0]},#{a[1]}]" set_mode :ground when rr # Set scrolling region # Enable scrolling entire display \e[r or just a region \e[<srow>;<erow>r a = @collect.split(";") if a.empty? # lines numbered from 1 # This should be 1 to n or the whole screen if no parms buf << "[SCRRESET]" else buf << "[SCRREG #{a[0]},#{a[1]}]" end set_mode :ground when JJ if @collect.to_i == 2 buf << "[CLEAR]" end set_mode :ground # Erase from cursor to end of screen Esc [ 0 J or Esc [ J # Erase from beginning of screen to cursor Esc [ 1 J # Erase entire screen Esc [ 2 J when KK if @collect.to_i == 2 buf << "[CLEARLINE]" end set_mode :ground # when ?K Erase line # Erase from cursor to end of line Esc [ 0 K or Esc [ K # Erase from beginning of line to cursor Esc [ 1 K # Erase line containing cursor Esc [ 2 K when GG buf << "[POS #{@collect.to_i == 0 ? 1 : @collect.to_i}]" set_mode :ground # when ?G Set starting column of presentation when gg, cc, hh, ll, ss, uu, xx, yy, qq, ii, pp # unhandled set_mode :ground # when ?c DA request/response - Device code - response is \e[<code>0c # when ?g Tab clear at current position # CSI 3 g is clear all tabs # when ?h Set mode # when ?l Reset mode # Enable Line Wrap <ESC>[7h # Disable Line Wrap <ESC>[7l # when ?s save cursor # when ?u unsave cursor # Same as ESC7 and ESC8 but not attributes? # when ?x Report terminal parameters # when ?y Confidence test # when ?q load LEDs # when ?i printing # when ?p Set Key Definition <ESC>[{key};"{string}"p # when ?P # -> set_mode :dcs when nn # Device status request i = @collect.to_i if i == 6 # Query cursor position - response is \e[<row>;<col>R # request for report cursor pos buf << "[CURREPT]" end set_mode :ground when mm # SGR color a = @collect.split(";") a.each do |cd| s = SGR2CODE[cd] buf << s if s end set_mode :ground when zz # MXP mode buf << "[MXP @collect.to_i]" set_mode :ground when ~~ # keys i = @collect.to_i case i when 1, 7 buf << "[HOME 1,1]" when 2 buf << "[INSERT]" when 3 # delete log.debug("(#{@pstack.conn.object_id}) BS, DEL found") buf.slice!(-1) || @pstack.conn.inbuffer.slice!(-1) echo(BS.chr) when 4, 8 buf << "[END]" when 5 buf << "[PAGEUP]" when 6 buf << "[PAGEDOWN]" when 11 buf << "[F1]" when 12 buf << "[F2]" when 13 buf << "[F3]" when 14 buf << "[F4]" when 15 buf << "[F5]" when 17 buf << "[F6]" when 18 buf << "[F7]" when 19 buf << "[F8]" when 20 buf << "[F9]" when 21 buf << "[F10]" end set_mode :ground # These cause immediate execution no matter what mode when ENQ, BEL, BS, TAB, VT, FF, SO, SI, DC1, DC3, CAN, SUB, DEL case b when BS, DEL log.debug("(#{@pstack.conn.object_id}) BS, DEL found") # slice local buffer or connection buffer buf.slice!(-1) || @pstack.conn.inbuffer.slice!(-1) when CAN, SUB @collect = "" set_mode :ground else buf << execute(b) end else @collect << b.chr end when :ss3 case b # Windows XP telnet when PP buf << "[F1]" when QQ buf << "[F2]" when RR buf << "[F3]" when SS buf << "[F4]" # /Windows XP telnet # ANSI cursor key mode when AA buf << "[UP 1]" when BB buf << "[DOWN 1]" when CC buf << "[RIGHT 1]" when DD buf << "[LEFT 1]" # /ANSI cursor key mode # These cause immediate execution no matter what mode when ENQ, BEL, BS, TAB, VT, FF, SO, SI, DC1, DC3, CAN, SUB, DEL case b when BS, DEL log.debug("(#{@pstack.conn.object_id}) BS, DEL found") # slice local buffer or connection buffer buf.slice!(-1) || @pstack.conn.inbuffer.slice!(-1) when CAN, SUB @collect = "" set_mode :ground else buf << execute(b) end end set_mode :ground end end # eachwhile b buf end
The #filter_out method filters output data
str
The string to be processed
return
The filtered data
# File lib/network/protocol/terminalfilter.rb, line 357 def filter_out(str) return '' if str.nil? || str.empty? case @pstack.terminal when %r^vt/, 'xterm' VTKeys.each do |key,val| while str =~ key do s = val.dup p1 = $1.dup if $1 p2 = $2.dup if $2 if p1 && p2 s.sub!(%r\$;\$/, "#{p1};#{p2}") elsif p1 s.sub!(%r\$/, p1) end str.sub!(key,s) end end else VTKeys.each do |key,val| while str =~ key do str.sub!(key,"") end end end str end
Run any post-contruction initialization
args
Optional initial options
# File lib/network/protocol/terminalfilter.rb, line 39 def init(args=nil) true end
Get current parse mode
return
The current parse mode
# File lib/network/protocol/terminalfilter.rb, line 418 def mode? return @mode end
set current parse mode
m
Mode to set it to
# File lib/network/protocol/terminalfilter.rb, line 424 def set_mode(m) @mode = m end