Created
January 24, 2017 07:46
-
-
Save djellemah/a0721c01167b36732b1de6ab3de1058b to your computer and use it in GitHub Desktop.
gtk2 sample with ruby-2.4
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /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