#!/usr/bin/env ruby require "time" require "ncurses" require "inifile" require "open3" class IO def readline_nonblock buffer = "" buffer << read_nonblock(1) while buffer[-1] != "\n" buffer rescue IO::WaitReadable => blocking if (not buffer.empty?) ungetc(buffer) end raise blocking end end class List_Win def initialize(inifile) @params = inifile @win = Ncurses::WINDOW.new(0, Ncurses.COLS()/4, 0, 0) @win.border(*([0]*8)) @win.keypad(true) @win.timeout(1000) @win.refresh() @entry = 0 end def getch() @win.getch() end def clear() @win.clear() end def resize(x,y) @win.resize(x,y) end def move(x,y) @win.move(x,y) end def refresh() @win.refresh() end def print_list(entry=nil) if(not entry.nil?) @entry = entry end i = 0 @params.each_section do |section| if(@entry == i) @win.attron(Ncurses::A_REVERSE) end @win.move(i+1,1) print_line(@win,@params[section]['Name']) if(@entry == i) @win.attroff(Ncurses::A_REVERSE) end i = i+1 end @win.border(*([0]*8)) @win.move(0,3) @win.addstr("Menu") @win.refresh() end end class Buff_Win def initialize(winsize,winpos,params) @params = params @win = Ncurses::WINDOW.new(0, winsize, 0, winpos) @panel = Ncurses::Panel::PANEL.new(@win) if(@params['Type'] == 'oneshot') @buffer = Buffer.new(0) else @buffer = Buffer.new(@params['Buffer'].to_i) end @proc = nil @curr_offset = 0 @hscroll = 0 update_date() spawn_proc() print_buffer() end def move_resize(winsize,winpos) newwin = Ncurses::WINDOW.new(0, winsize, 0, winpos) Ncurses::Panel.replace_panel(@panel, newwin) Ncurses.delwin(@win) @win = newwin print_buffer() end def update_date() @last_update = Time.now @date = @last_update.strftime("%F %R:%S") end def win_border() @win.border(*([0]*8)) win_header() end def win_header() @win.move(0,3) @win.addnstr(@params['Name'],@win.getmaxx-@date.length-10) @win.move(0,@win.getmaxx-@date.length-2) @win.addstr(@date) end def refresh() @win.refresh() end def clear() @win.clear() end def show_win() Ncurses::Panel.top_panel(@panel) Ncurses::Panel.update_panels end def hscroll(scroll=0) @hscroll += scroll if(@hscroll < 0) @hscroll = 0 end if(@hscroll > @buffer.maxlen()-@win.getmaxx+3) @hscroll = @buffer.maxlen()-@win.getmaxx+3 end print_buffer() refresh() end def scroll(scroll=0,goto=nil,fact=nil) if (not fact.nil?) scroll = (fact * @win.getmaxy.to_f).to_i elsif (not goto.nil?) @curr_offset = (goto * @buffer.size()).to_i scroll = 0 end #@curr_offset -= @win.getmaxy/2 @curr_offset += scroll if(@curr_offset < 0) @curr_offset = 0 end if(@curr_offset > @buffer.size()-@win.getmaxy+2) @curr_offset = @buffer.size()-@win.getmaxy+2 end print_buffer() refresh() end def print_buffer() #clear() win_border() j = 1 @buffer.yield(@win.getmaxy-2,@curr_offset) { |l,type| @win.move(j,1) if(type == 1) then @win.attron(Ncurses.COLOR_PAIR(1)) end print_line(@win,l,hscroll=@hscroll) if(type == 1) then @win.attroff(Ncurses.COLOR_PAIR(1)) end j = j+1 } if(@buffer.has_before?) @win.move(2,@win.getmaxx-1) @win.attron(Ncurses::A_REVERSE) @win.addstr("↑") @win.attroff(Ncurses::A_REVERSE) end if(@buffer.has_after?) @win.move(@win.getmaxy-2,@win.getmaxx-1) @win.attron(Ncurses::A_REVERSE) @win.addstr("↓") @win.attroff(Ncurses::A_REVERSE) end end def proc_readlines() begin while true @buffer.push(@proc.readline_nonblock) update_date() end rescue IO::WaitReadable end end def update(force=false) if(@params['Type'] == 'continuous') proc_readlines() end if(@params['Type'] == 'oneshot') if(force or (Time.now - @last_update > @params['Periodic'].to_i)) @buffer.clear() spawn_proc() end end print_buffer() end def spawn_proc() if(@params['Type'] == 'oneshot') update_date() Open3.popen3(@params["Command"]) { |i,o,e,t| while ((not o.eof?) or (not e.eof?)) rs = IO.select([o,e],nil)[0] r = (rs[0].eof?)? rs[1] : rs[0] if r.fileno == o.fileno @buffer.push(r.readline) elsif r.fileno == e.fileno @buffer.push(r.readline,type=1) end end } elsif(@params['Type'] == 'continuous') @proc = IO.popen(@params["Command"]) proc_readlines() end end end class Buffer def initialize(size) @size = size @buff = [] @buff_type = [] @current = 0 @wrap = false @before = false @after = false end def size() return @buff.length end def maxlen() maxlen = 0 @buff.each do |string| if string.length > maxlen maxlen = string.length end end return maxlen end def push(string,type=0) if(string.chomp.empty?) then string = " " end string.split( /\r?\n/ ).each do |line| @buff[@current] = line @buff_type[@current] = type if(@size > 0) @current = (1+@current) % @size else @current = 1+@current end if(@current == 0) then @wrap = true end end end def yield(size,offset=0,&block) if(size < 0) then size = 0 end range = Range.new(0,@current-1).to_a if(@wrap) range = Range.new(@current,@size-1).to_a + range end range = range.last(size+offset)[0,size] @before = (size+offset < @buff.length) @after = (offset != 0 and size < @buff.length) if(block) range.each do |i| yield [@buff[i],@buff_type[i]] end else return range.collect{|r| [@buff[r],@buff_type[r]]} end end def has_after?() return @after end def has_before?() return @before end def clear() @current = 0 @buff = [] @buff_type = [] end end def read_ini() inifile = IniFile.load('monitorrc') return inifile end def print_line(win, str, hscroll=0) revert_color = false str[0,5].match(/\033\[3(.)m/) { |c| #Line starts with an escape sequence. We handle that `a la xterm` Ncurses.init_pair(10, c[1].to_i, Ncurses::COLOR_BLACK) win.attron(Ncurses.COLOR_PAIR(10)) revert_color = true str = str[5,str.length] } str = str.gsub("\011"," ") #Any other control char is ignored and escaped str = str.gsub(/[[:cntrl:]]/) { |m| "^"+(m.ord + 64).chr } if(hscroll > 0) strcut = str[hscroll,str.length] if(strcut.nil? or strcut.empty?) str = "" else str = "…"+strcut end end strlen = str.length winlen = win.getmaxx-win.getcurx-1 if(strlen <= winlen) win.addstr(str + " "*(winlen-strlen)) else win.addstr(str[0,winlen-1]+"…") end if(revert_color) win.attroff(Ncurses.COLOR_PAIR(10)) end end def make_bufwins(inifile) bufwins = [] inifile.each_section do |section| bufwin = Buff_Win.new(Ncurses.COLS()-Ncurses.COLS()/4, Ncurses.COLS()/4, inifile[section]) bufwins.push(bufwin) end return bufwins end def update_buffers(bufwins) bufwins.each do |bufwin| bufwin.update() end end def redraw_all(list,bufwins,curr_bufwin) bufwins.each do |bufwin| bufwin.move_resize(Ncurses.COLS()-Ncurses.COLS()/4,Ncurses.COLS()/4) end list.resize(Ncurses.LINES(), Ncurses.COLS()/4) list.clear() list.print_list() list.refresh() curr_bufwin.refresh() end begin # initialize ncurses Ncurses.initscr Ncurses.start_color Ncurses.cbreak # provide unbuffered input Ncurses.noecho # turn off input echoing #Ncurses.nonl # turn off newline translation #Ncurses.stdscr.intrflush(false) # turn off flush-on-interrupt Ncurses.stdscr.keypad(true) # turn on keypad mode Ncurses.init_pair(1, Ncurses::COLOR_RED, Ncurses::COLOR_BLACK) Ncurses.init_pair(10, Ncurses::COLOR_WHITE, Ncurses::COLOR_BLACK) inifile = read_ini() list = List_Win.new(inifile) bufwins = make_bufwins(inifile) entry = 0 cur_bufwin = bufwins[entry] cur_bufwin.show_win() list.print_list() while(ch = list.getch()) do case(ch) when "n".ord entry = (entry +1) % bufwins.length cur_bufwin = bufwins[entry] cur_bufwin.show_win() list.print_list(entry=entry) when "p".ord entry = (entry -1) % bufwins.length cur_bufwin = bufwins[entry] cur_bufwin.show_win() list.print_list(entry=entry) when 12 #ctrl+L redraw_all(list,bufwins,cur_bufwin) when 18 #ctrl+R cur_bufwin.update(force=true) when Ncurses::KEY_RESIZE redraw_all(list,bufwins,cur_bufwin) when Ncurses::KEY_LEFT cur_bufwin.hscroll(scroll=-1) when Ncurses::KEY_RIGHT cur_bufwin.hscroll(scroll=1) when Ncurses::KEY_DOWN cur_bufwin.scroll(scroll=-1) when Ncurses::KEY_UP cur_bufwin.scroll(scroll=1) when Ncurses::KEY_NPAGE cur_bufwin.scroll(scroll=0,goto=nil,fact=-0.75) when Ncurses::KEY_PPAGE cur_bufwin.scroll(scroll=0,goto=nil,fact=0.75) when Ncurses::KEY_HOME cur_bufwin.scroll(scroll=0,goto=1.0) when Ncurses::KEY_END cur_bufwin.scroll(scroll=0,goto=0.0) when Ncurses::ERR update_buffers(bufwins) cur_bufwin.show_win() when "q".ord break else next end end ensure Ncurses.echo Ncurses.nocbreak Ncurses.nl Ncurses.endwin end