Skip to content

Instantly share code, notes, and snippets.

@djellemah
Created January 24, 2017 07:46
Show Gist options
  • Save djellemah/a0721c01167b36732b1de6ab3de1058b to your computer and use it in GitHub Desktop.
Save djellemah/a0721c01167b36732b1de6ab3de1058b to your computer and use it in GitHub Desktop.
gtk2 sample with ruby-2.4
#! /usr/bin/env ruby
# Some of the first ruby I wrote, in 2005. So it's not very idiomatic :-|
# GDK_POINTER_MOTION_HINT_MASK
require 'gtk2'
require 'pango'
require 'mathn'
require 'getoptlong'
# NOTE no space after comma, trailing comma is so
# that bold, italic etc and font size can be specified
FIXED_FONT = "Lucida Console,Consolas,Inconsolata,Courier New,"
DEBUG = false
# current size of data items to display, ie
# 4 bytes ie dwords
SIZE = 4
# require 'mmap'
# mmap doesn't compile with ruby-2.4.0,
# so just provide values so I can get the gtk stuff working
class Mmap
# was once a real constant
MAP_SHARED = 0
def initialize( path, mode, mmap_type, options )
end
def []( index, size )
# just dummy values vaguely related to index
([index % 256] * size).pack("C#{size}")
end
def size
1024
end
end
# create some new methods for this class
# these should really be in the c/c++ code
# because it's a lot better at the word size
# mainpulations
class Mmap
# fetch the specified number of bytes from the index
def get( index, size )
# fetch a string of length <size>
st = self[index,size]
# all these are unsigned
# C for byte
# S for short (two bytes)
# L for long (four bytes)
# See also Array#pack
case size
when 1
format = "C"
when 2
format = "S"
when 4
format = "L"
else
raise "unknown size " + size.to_s
end
return st.unpack(format)[0]
end
# set the value at the index using the specified
# number of bytes. Returns the array of bytes
def set( index, size, val )
bytes = Array.new(size)
( 0 ... size ) .each {
|i|
bval = ( val & ( 0xff << i*8) ) >> i*8
bytes[i] = bval
self[index + i] = bval
}
return bytes
end
end
options = {};
opts = GetoptLong.new
opts.set_options(
['--offset', '-o', GetoptLong::OPTIONAL_ARGUMENT ],
['--length', '-l', GetoptLong::OPTIONAL_ARGUMENT ],
['--size', '-s', GetoptLong::OPTIONAL_ARGUMENT ]
)
opts.each_option do | name, arg |
print "name: ", name, " arg: ", arg, "\n"
# offset
if ( name == '--offset' )
options["offset"] = arg.to_i
end
# length
if ( name == '--length' )
options["length"] = arg.to_i
end
# size of each item, in bytes
if ( name == '--size' )
SIZE = arg.to_i
end
end
mmap = Mmap::new( ARGV[0], "rw", Mmap::MAP_SHARED, options )
class String
# convert a string containing "0" and "1"
# into an integer
# .to_i(2) would be better.
def bin
val = 0
rev = self.reverse
( 0 ... rev.size ).each {
|i|
# we want the character at [i], not
# the ascii code at [i]
# hence rev[i,1]
# using the if is slightly more efficient that
# doing it the mathematical way
case rev[i,1]
when "1"
val += ( 2 ** i )
when "0"
else
raise self + " is not a binary string"
end
}
return val
end
end
Gtk.init
window = Gtk::Window.new
vbox = Gtk::VBox.new( false )
# create a status bar
status = Gtk::Statusbar.new
# Create data model. Stores only the address.
# the values at the addresses are fetched from
# the mmap on the fly
# in other words iter[0] will always be the address
model = Gtk::ListStore.new( Integer )
# initialize the model
( 0 ... mmap.size() ).step( SIZE ) {
|n|
model.append()[0] = n
}
# Create view
tv = Gtk::TreeView.new(model)
# handle mouse motion events so we can display what binary
# digit the mouse is currently over
tv.signal_connect("motion-notify-event") {
|widget,event|
res = tv.get_path_at_pos(event.x,event.y)
# do we have an entry here?
if res != nil
# calculate the digit number, assuming
# we're working with a fixed-pitch font
# might be able to use PAngo::Font to get font metrics
path = res[0]
column = res[1]
# are we in the binary column?
if column.title == "binary"
cellx = res[2]
celly = res[3]
cell_area = tv.get_cell_area( path, column );
digit = ( SIZE * 8 - ( cellx / cell_area.width ) * SIZE * 8 ).to_i
# show the current binary digit
status.pop( path.to_s.to_i )
status.push( path.to_s.to_i, " bit number: " + digit.to_s )
else
# clear the status bar
status.pop( path.to_s.to_i )
status.push( path.to_s.to_i, "" )
end
else
# clear the status bar
status.pop( 0 )
status.push( 0, "" )
end
}
# column 1 - the address column
renderer = Gtk::CellRendererText.new
column = Gtk::TreeViewColumn.new("address", renderer )
column.resizable = true
renderer.font = FIXED_FONT
column.set_cell_data_func(renderer) {
|treeViewColumn, cellRenderer, model, iter|
cellRenderer.text = sprintf( "%8.8x", iter[0] )
}
tv.append_column(column)
# column 2 - the hex display column
renderer = Gtk::CellRendererText.new
renderer.mode = Gtk::CellRendererText::MODE_EDITABLE
renderer.editable = true
renderer.signal_connect("editing-started") do |widget,editable,path|
print "edit widget: ", widget.to_s, "\n" if DEBUG
widget.font = FIXED_FONT
it = model.get_iter(path)
# keep the index currently being edited
index = it[0]
# update things once editing is finished for this cell
editable.signal_connect("editing-done") {
# this is the widget on which the edit has been finished
|w|
val = editable.text.hex
print "index ", index, "=>", val, "\n" if DEBUG
# store the new value in the mmap
mmap.set( index, SIZE, val )
print sprintf( "val: %8.8x\n", val ) if DEBUG
}
end
renderer.font = FIXED_FONT
column = Gtk::TreeViewColumn.new("hex", renderer )
column.set_cell_data_func(renderer) {
|treeViewColumn, cellRenderer, model, iter|
hexits = SIZE * 2
cellRenderer.text = sprintf( "%#{hexits}.#{hexits}x", mmap.get( iter[0], SIZE ) )
}
tv.append_column(column)
# column 3 - the binary display column
renderer = Gtk::CellRendererText.new
renderer.mode = Gtk::CellRendererText::MODE_EDITABLE
renderer.editable = true
renderer.font = FIXED_FONT
renderer.signal_connect("editing-started") do |widget,editable,path|
widget.font = FIXED_FONT
print "edit widget: ", widget.to_s, "\n" if DEBUG
it = model.get_iter(path)
# keep the index currently being edited
index = it[0]
# update things once editing is finished for this cell
editable.signal_connect("editing-done") {
# this is the widget on which the edit has been finished
|w|
val = editable.text.bin
print "index ", index, "=>", val, "\n" if DEBUG
# store the new value in the mmap
mmap.set( index, SIZE, val )
print sprintf( "val: %8.8x\n", val ) if DEBUG
}
end
column = Gtk::TreeViewColumn.new("binary", renderer )
column.set_cell_data_func(renderer) {
|treeViewColumn, cellRenderer, model, iter|
binits = SIZE * 8
cellRenderer.text = sprintf( "%#{binits}.#{binits}b", mmap.get( iter[0], SIZE ) )
}
tv.append_column(column)
# column 4 - the ascii display column
renderer = Gtk::CellRendererText.new
column = Gtk::TreeViewColumn.new("ascii", renderer )
column.set_cell_data_func(renderer) {
|treeViewColumn, cellRenderer, model, iter|
# this just fetches a string of size SIZE from the
# given address
bytes = mmap[iter[0],SIZE]
cellRenderer.text = bytes.each_char.map{|b| "%x" % b.unpack('C1').first}.join
}
renderer.font = FIXED_FONT
tv.append_column(column)
################
# Main script
# add scrollbars
scroll = Gtk::ScrolledWindow.new( nil, nil )
scroll.add( tv )
# expand and fill
vbox.pack_start( scroll, true, true )
# don't expand and fill
vbox.pack_start( status, false, false )
window.add( vbox )
window.set_default_size(800, 600).show_all
# The program will directly end upon 'delete_event', ie window close
window.signal_connect('delete_event') do
Gtk.main_quit
false
end
# and off we go...
Gtk.main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment