From 05e0c8df4d7cad09631ea3b5bd86dfe1165ffd31 Mon Sep 17 00:00:00 2001 From: TellowKrinkle Date: Sun, 6 Dec 2020 19:29:50 -0600 Subject: [PATCH 1/4] Add curses console --- lib/kaitai/console_ansi.rb | 15 ++++ lib/kaitai/console_curses.rb | 148 ++++++++++++++++++++++++++++++++++ lib/kaitai/console_windows.rb | 15 +++- 3 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 lib/kaitai/console_curses.rb diff --git a/lib/kaitai/console_ansi.rb b/lib/kaitai/console_ansi.rb index 6d9942b..51c68f8 100644 --- a/lib/kaitai/console_ansi.rb +++ b/lib/kaitai/console_ansi.rb @@ -35,6 +35,21 @@ def clear print @seq_clear end + def refresh + end + + def print(*args) + Kernel.print(*args) + end + + def puts(*args) + Kernel.puts(*args) + end + + def readline + Readline.readline('', false) + end + # Put the cursor up to screen position (x, y). First line is 0, first column is 0. def goto(x, y) # print `tput cup #{y} #{x}` diff --git a/lib/kaitai/console_curses.rb b/lib/kaitai/console_curses.rb new file mode 100644 index 0000000..0e4344a --- /dev/null +++ b/lib/kaitai/console_curses.rb @@ -0,0 +1,148 @@ +require 'curses' + +module Kaitai + class ConsoleCurses + def initialize + Curses.init_screen + Curses.start_color + Curses.use_default_colors + Curses.stdscr.keypad = true + ObjectSpace.define_finalizer(self, proc { Curses.close_screen }) + + @on_resize = nil + @color_pairs = {} + @fg_color = COLORS[:default] + @bg_color = COLORS[:default] + update_color + end + + attr_writer :on_resize + + def rows + Curses.lines + end + + def cols + Curses.cols + end + + private def update_color + color = @color_pairs[[@fg_color, @bg_color]] + if color.nil? then + color = @color_pairs.size + @color_pairs[[@fg_color, @bg_color]] = color + Curses.init_pair(color, @fg_color, @bg_color) + end + Curses.attrset(Curses.color_pair(color)) + end + + def fg_color=(col) + code = COLORS[col] + raise "Invalid color: #{col}" unless code + @fg_color = code + update_color + end + + def bg_color=(col) + code = COLORS[col] + raise "Invalid color: #{col}" unless code + @bg_color = code + update_color + end + + def reset_colors + @fg_color = COLORS[:default] + @bg_color = COLORS[:default] + update_color + end + + def read_char + loop do + char = Curses.getch + if char == Curses::Key::RESIZE then + @on_resize&.call(true) + else + return char + end + end + end + + def read_char_mapped + c = read_char + KEY_MAP[c] || c + end + + def goto(x, y) + Curses.setpos(y, x) + end + + def clear + Curses.clear + end + + def refresh + Curses.refresh + end + + def readline + Curses.getstr + end + + def print(*args) + args.each do |arg| + Curses.addstr(arg) + end + end + + def puts(*args) + Curses.addstr("\n") if args.empty? + args.each do |arg| + Curses.addstr(arg) + Curses.addstr("\n") + end + end + + COLORS = { + default: -1, + black: 0, + gray: 7, + gray0: 232, + gray1: 233, + gray2: 234, + gray3: 235, + gray4: 236, + gray5: 237, + gray6: 238, + gray7: 239, + gray8: 240, + gray9: 241, + gray10: 242, + gray11: 243, + gray12: 244, + gray13: 245, + gray14: 246, + gray15: 247, + gray16: 248, + gray17: 249, + gray18: 250, + gray19: 251, + gray20: 252, + gray21: 253, + gray22: 254, + gray23: 255, + }.freeze + + KEY_MAP = { + 9 => :tab, + 10 => :enter, + Curses::Key::UP => :up_arrow, + Curses::Key::DOWN => :down_arrow, + Curses::Key::LEFT => :left_arrow, + Curses::Key::RIGHT => :right_arrow, + Curses::Key::PPAGE => :pg_up, + Curses::Key::NPAGE => :pg_dn, + Curses::Key::HOME => :home, + Curses::Key::END => :end, + }.freeze + end +end diff --git a/lib/kaitai/console_windows.rb b/lib/kaitai/console_windows.rb index dd6f104..5ac36cf 100644 --- a/lib/kaitai/console_windows.rb +++ b/lib/kaitai/console_windows.rb @@ -56,13 +56,24 @@ def load_term_size attr_writer :on_resize - def puts(s) - Kernel.puts s + def refresh + end + + def print(*args) + Kernel.print(*args) + end + + def puts(*args) + Kernel.puts(*args) # num_written = 'XXXX' # reserved = 'XXXX' # WRITE_CONSOLE.call(@stdout_handle, s, s.length, num_written, reserved) end + def readline + Readline.readline('', false) + end + def clear con_size = @buf_cols * @buf_rows num_written = 'XXXX'.dup From 8e064cfbe739a78632530f34c2565f29bd0672ce Mon Sep 17 00:00:00 2001 From: TellowKrinkle Date: Sun, 6 Dec 2020 19:31:41 -0600 Subject: [PATCH 2/4] Make all printing go through console Required for curses --- lib/kaitai/struct/visualizer/hex_viewer.rb | 8 ++++---- lib/kaitai/struct/visualizer/node.rb | 22 +++++++++++----------- lib/kaitai/struct/visualizer/tree.rb | 5 +++-- lib/kaitai/tui.rb | 8 ++++++-- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/lib/kaitai/struct/visualizer/hex_viewer.rb b/lib/kaitai/struct/visualizer/hex_viewer.rb index b24a88b..41d68f0 100644 --- a/lib/kaitai/struct/visualizer/hex_viewer.rb +++ b/lib/kaitai/struct/visualizer/hex_viewer.rb @@ -58,7 +58,7 @@ def run c = nil loop do @ui.goto(0, @max_scr_ln + 1) - printf '%08x (%d, %d)', @addr, @cur_x, @cur_y + @ui.printf '%08x (%d, %d)', @addr, @cur_x, @cur_y @ui.goto(col_to_col_char(@cur_x), row_to_scr(@cur_y)) c = @ui.read_char_mapped @@ -181,7 +181,7 @@ def redraw hex = line.bytes.map { |x| format('%02x', x) }.join(' ') char = line.bytes.map { |x| byte_to_display_char(x) }.join - printf FMT, i, hex, char + @ui.printf FMT, i, hex, char i += PER_LINE row += 1 end @@ -237,7 +237,7 @@ def highlight_draw_hex(r, c, i, p2) v = byte_at(i) return if v.nil? - printf('%02x ', v) + @ui.printf('%02x ', v) c += 1 if c >= PER_LINE c = 0 @@ -257,7 +257,7 @@ def highlight_draw_char(r, c, i, p2) v = byte_at(i) return if v.nil? - print byte_to_display_char(v) + @ui.print byte_to_display_char(v) c += 1 if c >= PER_LINE c = 0 diff --git a/lib/kaitai/struct/visualizer/node.rb b/lib/kaitai/struct/visualizer/node.rb index 39d026f..b461d51 100644 --- a/lib/kaitai/struct/visualizer/node.rb +++ b/lib/kaitai/struct/visualizer/node.rb @@ -76,8 +76,8 @@ def close end def draw(_ui) - print ' ' * level - print(if @value.nil? + _ui.print ' ' * level + _ui.print(if @value.nil? '[?]' elsif open? '[-]' @@ -86,17 +86,17 @@ def draw(_ui) else '[.]' end) - print " #{@id}" + _ui.print " #{@id}" pos = 2 * level + 4 + @id.length if open? || !openable? if @value.is_a?(Float) || @value.is_a?(Integer) - print " = #{@value}" + _ui.print " = #{@value}" elsif @value.is_a?(Symbol) - print " = #{@value}" + _ui.print " = #{@value}" elsif @value.is_a?(String) - print ' = ' + _ui.print ' = ' pos += 3 @str_mode ||= detect_str_mode max_len = @tree.tree_width - pos @@ -119,17 +119,17 @@ def draw(_ui) s = s[0, max_len - 1] || '' s += '…' end - print s + _ui.print s elsif (@value == true) || (@value == false) - print " = #{@value}" + _ui.print " = #{@value}" elsif @value.nil? - print ' = null' + _ui.print ' = null' elsif @value.is_a?(Array) - printf ' (%d = 0x%x entries)', @value.size, @value.size + _ui.printf ' (%d = 0x%x entries)', @value.size, @value.size end end - puts + _ui.puts end def first_n_bytes_dump(s, n) diff --git a/lib/kaitai/struct/visualizer/tree.rb b/lib/kaitai/struct/visualizer/tree.rb index d12f6e5..370b123 100644 --- a/lib/kaitai/struct/visualizer/tree.rb +++ b/lib/kaitai/struct/visualizer/tree.rb @@ -68,9 +68,10 @@ def run end @ui.goto(0, @max_scr_ln + 1) - printf 'all: %d, tree: %d, tree_draw: %d, hexview: %d, ln: %d, ', (t + thv) * 1e6, t * 1e6, @draw_time * 1e6, thv * 1e6, @ln - puts "highlight = #{@cur_node.pos1}..#{@cur_node.pos2}" + @ui.printf 'all: %d, tree: %d, tree_draw: %d, hexview: %d, ln: %d, ', (t + thv) * 1e6, t * 1e6, @draw_time * 1e6, thv * 1e6, @ln + @ui.puts "highlight = #{@cur_node.pos1}..#{@cur_node.pos2}" # puts "keypress: #{c.inspect}" + @ui.refresh begin process_keypress diff --git a/lib/kaitai/tui.rb b/lib/kaitai/tui.rb index 89177bf..5826441 100644 --- a/lib/kaitai/tui.rb +++ b/lib/kaitai/tui.rb @@ -5,7 +5,7 @@ module Kaitai class TUI extend Forwardable - def_delegators :@console, :rows, :cols, :goto, :clear, :fg_color=, :bg_color=, :reset_colors, :read_char_mapped + def_delegators :@console, :rows, :cols, :goto, :clear, :fg_color=, :bg_color=, :reset_colors, :read_char_mapped, :refresh, :print, :puts attr_reader :highlight_colors @@ -61,7 +61,7 @@ def input_str(header, _msg) print ' ', header, ' ' goto(11, top_y + 1) - Readline.readline('', false) + @console.readline end def draw_rectangle(x, y, w, h, charset = DOUBLE_CHARSET) @@ -88,6 +88,10 @@ def draw_button(x, y, _w, caption) puts "[ #{caption} ]" end + def printf(*args) + @console.print sprintf(*args) + end + # Regexp borrowed from # http://stackoverflow.com/questions/170956/how-can-i-find-which-operating-system-my-ruby-program-is-running-on @@is_windows = (RUBY_PLATFORM =~ /cygwin|mswin|mingw|bccwin|wince|emx/) ? true : false From 15fa932331b00c5b9de56cd07f413b87cf004d2c Mon Sep 17 00:00:00 2001 From: TellowKrinkle Date: Sun, 6 Dec 2020 19:32:05 -0600 Subject: [PATCH 3/4] Use curses console if curses is available --- lib/kaitai/tui.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/kaitai/tui.rb b/lib/kaitai/tui.rb index 5826441..8ff83e1 100644 --- a/lib/kaitai/tui.rb +++ b/lib/kaitai/tui.rb @@ -15,8 +15,13 @@ def initialize @console = ConsoleWindows.new @highlight_colors = %i[white aqua blue green white] else - require 'kaitai/console_ansi' - @console = ConsoleANSI.new + begin + require 'kaitai/console_curses' + @console = ConsoleCurses.new + rescue LoadError + require 'kaitai/console_ansi' + @console = ConsoleANSI.new + end @highlight_colors = %i[gray14 gray11 gray8 gray5 gray2] end end From 45a61a7ec103b0ded7a89fa1313379da9c4154e0 Mon Sep 17 00:00:00 2001 From: TellowKrinkle Date: Sun, 6 Dec 2020 20:06:05 -0600 Subject: [PATCH 4/4] Properly resize hex viewer on window resize --- lib/kaitai/struct/visualizer/hex_viewer.rb | 6 +++++- lib/kaitai/struct/visualizer/tree.rb | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/kaitai/struct/visualizer/hex_viewer.rb b/lib/kaitai/struct/visualizer/hex_viewer.rb index 41d68f0..074f05c 100644 --- a/lib/kaitai/struct/visualizer/hex_viewer.rb +++ b/lib/kaitai/struct/visualizer/hex_viewer.rb @@ -13,7 +13,7 @@ def initialize(ui, buf, tree = nil) @tree = tree @embedded = !tree.nil? - @max_scr_ln = @ui.rows - 3 + recalc_sizes @addr = 0 @scroll_y = 0 @@ -21,6 +21,10 @@ def initialize(ui, buf, tree = nil) raise if @cur_x.nil? end + def recalc_sizes + @max_scr_ln = @ui.rows - 3 + end + attr_writer :buf attr_reader :addr diff --git a/lib/kaitai/struct/visualizer/tree.rb b/lib/kaitai/struct/visualizer/tree.rb index 370b123..132efa1 100644 --- a/lib/kaitai/struct/visualizer/tree.rb +++ b/lib/kaitai/struct/visualizer/tree.rb @@ -26,6 +26,7 @@ def initialize(ui, st) @ui.on_resize = proc { |redraw_needed| recalc_sizes + @hv.recalc_sizes redraw if redraw_needed @hv.redraw if redraw_needed }