Commit d12da209796dc500bd54f94e852f566521d15f9a

Authored by Marius Hanne
1 parent 966c5ec068

simple gtk gui WIP

Showing 12 changed files with 1026 additions and 28 deletions Side-by-side Diff

... ... @@ -45,7 +45,7 @@
45 45  
46 46 EM.connect(host, port) do |connection|
47 47  
48   - connection.send_data([ARGV[0], ARGV[1..-1].join(" ")].to_json)
  48 + connection.send_data([ARGV[0], ARGV[1..-1].join(" ")].to_json + "\x00")
49 49  
50 50 def connection.receive_data(data)
51 51 (@buf ||= BufferedTokenizer.new("\x00")).extract(data).each do |packet|
  1 +#!/usr/bin/env ruby
  2 +$:.unshift( File.expand_path("../../lib", __FILE__) )
  3 +
  4 +require 'gtk3'
  5 +require 'eventmachine'
  6 +require 'bitcoin'
  7 +require 'optparse'
  8 +require 'json'
  9 +require 'monitor'
  10 +
  11 +defaults = {
  12 + :network => "testnet",
  13 + :storage => "dummy",
  14 + :keystore => "dummy",
  15 + :command => "127.0.0.1:9999"
  16 +}
  17 +options = Bitcoin::Config.load(defaults, :wallet)
  18 +
  19 +optparse = OptionParser.new do |opts|
  20 + opts.banner =
  21 + "Usage: bitcoin_wallet [options] <command> [<command options>]\n"
  22 +
  23 + opts.separator("\nAvailable options:\n")
  24 +
  25 + opts.on("-c", "--config FILE",
  26 + "Config file (default: #{Bitcoin::Config::CONFIG_PATHS})") do |file|
  27 + options = Bitcoin::Config.load_file(options, file, :wallet)
  28 + end
  29 +
  30 + opts.on("-n", "--network NETWORK",
  31 + "User Network (default: #{options[:network]})") do |network|
  32 + options[:network] = network
  33 + end
  34 +
  35 + opts.on("-s", "--storage BACKEND::CONFIG",
  36 + "Use storage backend (default: #{options[:storage]})") do |storage|
  37 + options[:storage] = storage
  38 + end
  39 +
  40 + opts.on("--command [HOST:PORT]",
  41 + "Node command socket (default: #{options[:command]})") do |command|
  42 + options[:command] = command
  43 + end
  44 +
  45 + opts.on("-k", "--keystore [backend::<config>]",
  46 + "Key store (default: #{options[:store]})") do |store|
  47 + options[:keystore] = store
  48 + end
  49 +
  50 + opts.on("-h", "--help", "Display this help") do
  51 + puts opts; exit
  52 + end
  53 +
  54 + opts.separator "\nAvailable commands:\n" +
  55 + " balance [<addr>] - display balance for given addr or whole wallet\n" +
  56 + " list <addr> - list transaction history for address\n" +
  57 + " send <addr>:<amount>[,<addr>:<amount>...] [<fee>] - send transaction\n" +
  58 + " new - generate new key and add to keystore\n" +
  59 + " import <base58> - import key in base58 format\n" +
  60 + " export <addr> - export key to base58 format\n"
  61 +
  62 +end
  63 +
  64 +optparse.parse!
  65 +
  66 +
  67 +
  68 +backend, config = options[:storage].split("::")
  69 +@storage = Bitcoin::Storage.send(backend, :db => config)
  70 +
  71 +backend, config = options[:keystore].split("::")
  72 +config = Hash[config.split(",").map{|c| c.split("=")}]
  73 +
  74 +EM.run do
  75 + @gui = Bitcoin::Gui::Gui.new(@storage, config['file'])
  76 + Bitcoin::Gui::Connection.connect(*options[:command].split(":"), @gui)
  77 + EM.gtk_main
  78 +end
... ... @@ -33,6 +33,11 @@
33 33 autoload :TxDP, 'bitcoin/wallet/txdp'
34 34 end
35 35  
  36 + module Gui
  37 + autoload :Gui, 'bitcoin/gui/gui'
  38 + autoload :Connection, 'bitcoin/gui/connection'
  39 + end
  40 +
36 41 module Util
37 42  
38 43 def hth(h); h.unpack("H*")[0]; end
lib/bitcoin/gui/connection.rb
  1 +module Bitcoin::Gui
  2 + class Connection < EM::Connection
  3 +
  4 + attr_reader :info
  5 +
  6 + def initialize gtk
  7 + @gtk = gtk
  8 + @gtk.node = self
  9 + @buf = BufferedTokenizer.new("\x00")
  10 + end
  11 +
  12 + def gui &block
  13 + EM.next_tick do
  14 + @gtk.instance_eval &block
  15 + end
  16 + end
  17 +
  18 + def post_init
  19 + p 'connected'
  20 + query("info")
  21 + query("monitor", "block connection")
  22 + end
  23 +
  24 + def query(cmd, args = "")
  25 + puts "query: #{cmd}"
  26 + send_data([cmd, args].to_json + "\x00")
  27 + end
  28 +
  29 + def receive_data data
  30 + @buf.extract(data).each do |packet|
  31 + # p packet
  32 + cmd, data = *JSON.load(packet)
  33 + puts "data: #{cmd}"
  34 + case cmd
  35 + when 'info'
  36 + text = "connections: #{data['connections']} | " +
  37 + "addrs: #{data['addrs']} | uptime: #{data['uptime']}"
  38 + gui { status_network.push 0, text }
  39 + #EM::defer { sleep(1) && query("info") }
  40 +
  41 + when 'monitor'
  42 + EM.defer do
  43 + begin
  44 + type, data = *data
  45 + case type
  46 + when "block"
  47 + gui { status_store.push 0, "#{depth}" }
  48 + when "tx"
  49 + when "connection"
  50 + next unless data
  51 + conn_type, data = *data
  52 + if conn_type == "connected"
  53 + row = @gtk.conn_store.append(nil)
  54 + row[0] = data['host']
  55 + row[1] = data['port']
  56 + row[2] = data['state']
  57 + row[3] = data['version']
  58 + row[4] = data['block']
  59 + row[5] = data['started']
  60 + row[6] = data['user_agent']
  61 + gui { conn_view.model = conn_store }
  62 + elsif conn_type == "disconnected"
  63 + iter = nil
  64 + @gtk.conn_store.each do |model,path,i|
  65 + iter = i if i[0] == data[0] && i[1] == data[1].to_s
  66 + end
  67 + if iter
  68 + @gtk.conn_store.remove(iter)
  69 + gui { conn_view.model = conn_store}
  70 + end
  71 + end
  72 + when "addr"
  73 + else
  74 + puts "invalid datatype: #{type.inspect}"
  75 + end
  76 + rescue
  77 + puts "Error reading command: #{cmd}(#{data.inspect})"
  78 + p $!
  79 + puts *$@
  80 + end
  81 + end
  82 + end
  83 + end
  84 + end
  85 +
  86 + def unbind
  87 + puts "disconnected"
  88 + end
  89 +
  90 + def self.connect host, port, gtk
  91 + EM.connect(host, port, self, gtk)
  92 + end
  93 + end
  94 +end
lib/bitcoin/gui/em_gtk.rb
  1 +module Gtk
  2 + GTK_PENDING_BLOCKS = []
  3 + GTK_PENDING_BLOCKS_LOCK = Monitor.new
  4 +
  5 + def Gtk.queue &block
  6 + GTK_PENDING_BLOCKS_LOCK.synchronize do
  7 + GTK_PENDING_BLOCKS << block
  8 + end
  9 + end
  10 +
  11 + def Gtk.main_iteration_with_queue
  12 + GTK_PENDING_BLOCKS_LOCK.synchronize do
  13 + for block in GTK_PENDING_BLOCKS
  14 + EM.next_tick { block.call }
  15 + end
  16 + GTK_PENDING_BLOCKS.clear
  17 + end
  18 + Gtk.main_iteration if Gtk.events_pending?
  19 + end
  20 +end
  21 +
  22 +module EM
  23 + def self.gtk_main
  24 + give_tick = proc do
  25 + Gtk.main_iteration_with_queue
  26 + EM.defer do
  27 + sleep 0.001
  28 + EM.next_tick give_tick
  29 + end
  30 + end
  31 + give_tick.call
  32 + end
  33 +end
lib/bitcoin/gui/gui.builder
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<interface>
  3 + <!-- interface-requires gtk+ 3.0 -->
  4 + <object class="GtkImage" id="image_new_addr">
  5 + <property name="visible">True</property>
  6 + <property name="can_focus">False</property>
  7 + <property name="stock">gtk-add</property>
  8 + </object>
  9 + <object class="GtkImage" id="image_new_tx">
  10 + <property name="visible">True</property>
  11 + <property name="can_focus">False</property>
  12 + <property name="stock">gtk-go-forward</property>
  13 + </object>
  14 + <object class="GtkWindow" id="main_window">
  15 + <property name="can_focus">False</property>
  16 + <signal name="delete-event" handler="on_exit" swapped="no"/>
  17 + <child>
  18 + <object class="GtkBox" id="box1">
  19 + <property name="visible">True</property>
  20 + <property name="can_focus">False</property>
  21 + <property name="orientation">vertical</property>
  22 + <child>
  23 + <object class="GtkMenuBar" id="menubar1">
  24 + <property name="visible">True</property>
  25 + <property name="can_focus">False</property>
  26 + <child>
  27 + <object class="GtkMenuItem" id="menuitem1">
  28 + <property name="use_action_appearance">False</property>
  29 + <property name="visible">True</property>
  30 + <property name="can_focus">False</property>
  31 + <property name="label" translatable="yes">_File</property>
  32 + <property name="use_underline">True</property>
  33 + <child type="submenu">
  34 + <object class="GtkMenu" id="menu1">
  35 + <property name="visible">True</property>
  36 + <property name="can_focus">False</property>
  37 + <child>
  38 + <object class="GtkImageMenuItem" id="imagemenuitem1">
  39 + <property name="label">gtk-new</property>
  40 + <property name="use_action_appearance">False</property>
  41 + <property name="visible">True</property>
  42 + <property name="can_focus">False</property>
  43 + <property name="use_underline">True</property>
  44 + <property name="use_stock">True</property>
  45 + <signal name="activate" handler="on_new_wallet" swapped="no"/>
  46 + </object>
  47 + </child>
  48 + <child>
  49 + <object class="GtkImageMenuItem" id="imagemenuitem2">
  50 + <property name="label">gtk-open</property>
  51 + <property name="use_action_appearance">False</property>
  52 + <property name="visible">True</property>
  53 + <property name="can_focus">False</property>
  54 + <property name="use_underline">True</property>
  55 + <property name="use_stock">True</property>
  56 + <signal name="activate" handler="on_open_wallet" swapped="no"/>
  57 + </object>
  58 + </child>
  59 + <child>
  60 + <object class="GtkImageMenuItem" id="imagemenuitem3">
  61 + <property name="label">gtk-save</property>
  62 + <property name="use_action_appearance">False</property>
  63 + <property name="visible">True</property>
  64 + <property name="can_focus">False</property>
  65 + <property name="use_underline">True</property>
  66 + <property name="use_stock">True</property>
  67 + </object>
  68 + </child>
  69 + <child>
  70 + <object class="GtkImageMenuItem" id="imagemenuitem4">
  71 + <property name="label">gtk-save-as</property>
  72 + <property name="use_action_appearance">False</property>
  73 + <property name="visible">True</property>
  74 + <property name="can_focus">False</property>
  75 + <property name="use_underline">True</property>
  76 + <property name="use_stock">True</property>
  77 + </object>
  78 + </child>
  79 + <child>
  80 + <object class="GtkSeparatorMenuItem" id="separatormenuitem1">
  81 + <property name="use_action_appearance">False</property>
  82 + <property name="visible">True</property>
  83 + <property name="can_focus">False</property>
  84 + </object>
  85 + </child>
  86 + <child>
  87 + <object class="GtkImageMenuItem" id="imagemenuitem5">
  88 + <property name="label">gtk-quit</property>
  89 + <property name="use_action_appearance">False</property>
  90 + <property name="visible">True</property>
  91 + <property name="can_focus">False</property>
  92 + <property name="use_underline">True</property>
  93 + <property name="use_stock">True</property>
  94 + <signal name="activate" handler="on_exit" swapped="no"/>
  95 + </object>
  96 + </child>
  97 + </object>
  98 + </child>
  99 + </object>
  100 + </child>
  101 + <child>
  102 + <object class="GtkMenuItem" id="menuitem2">
  103 + <property name="use_action_appearance">False</property>
  104 + <property name="visible">True</property>
  105 + <property name="can_focus">False</property>
  106 + <property name="label" translatable="yes">_Edit</property>
  107 + <property name="use_underline">True</property>
  108 + <child type="submenu">
  109 + <object class="GtkMenu" id="menu2">
  110 + <property name="visible">True</property>
  111 + <property name="can_focus">False</property>
  112 + <child>
  113 + <object class="GtkImageMenuItem" id="imagemenuitem6">
  114 + <property name="label">gtk-cut</property>
  115 + <property name="use_action_appearance">False</property>
  116 + <property name="visible">True</property>
  117 + <property name="can_focus">False</property>
  118 + <property name="use_underline">True</property>
  119 + <property name="use_stock">True</property>
  120 + </object>
  121 + </child>
  122 + <child>
  123 + <object class="GtkImageMenuItem" id="imagemenuitem7">
  124 + <property name="label">gtk-copy</property>
  125 + <property name="use_action_appearance">False</property>
  126 + <property name="visible">True</property>
  127 + <property name="can_focus">False</property>
  128 + <property name="use_underline">True</property>
  129 + <property name="use_stock">True</property>
  130 + </object>
  131 + </child>
  132 + <child>
  133 + <object class="GtkImageMenuItem" id="imagemenuitem8">
  134 + <property name="label">gtk-paste</property>
  135 + <property name="use_action_appearance">False</property>
  136 + <property name="visible">True</property>
  137 + <property name="can_focus">False</property>
  138 + <property name="use_underline">True</property>
  139 + <property name="use_stock">True</property>
  140 + </object>
  141 + </child>
  142 + <child>
  143 + <object class="GtkImageMenuItem" id="imagemenuitem9">
  144 + <property name="label">gtk-delete</property>
  145 + <property name="use_action_appearance">False</property>
  146 + <property name="visible">True</property>
  147 + <property name="can_focus">False</property>
  148 + <property name="use_underline">True</property>
  149 + <property name="use_stock">True</property>
  150 + </object>
  151 + </child>
  152 + </object>
  153 + </child>
  154 + </object>
  155 + </child>
  156 + <child>
  157 + <object class="GtkMenuItem" id="menuitem3">
  158 + <property name="use_action_appearance">False</property>
  159 + <property name="visible">True</property>
  160 + <property name="can_focus">False</property>
  161 + <property name="label" translatable="yes">_View</property>
  162 + <property name="use_underline">True</property>
  163 + </object>
  164 + </child>
  165 + <child>
  166 + <object class="GtkMenuItem" id="menuitem4">
  167 + <property name="use_action_appearance">False</property>
  168 + <property name="visible">True</property>
  169 + <property name="can_focus">False</property>
  170 + <property name="label" translatable="yes">_Help</property>
  171 + <property name="use_underline">True</property>
  172 + <child type="submenu">
  173 + <object class="GtkMenu" id="menu3">
  174 + <property name="visible">True</property>
  175 + <property name="can_focus">False</property>
  176 + <child>
  177 + <object class="GtkImageMenuItem" id="imagemenuitem10">
  178 + <property name="label">gtk-about</property>
  179 + <property name="use_action_appearance">False</property>
  180 + <property name="visible">True</property>
  181 + <property name="can_focus">False</property>
  182 + <property name="use_underline">True</property>
  183 + <property name="use_stock">True</property>
  184 + </object>
  185 + </child>
  186 + </object>
  187 + </child>
  188 + </object>
  189 + </child>
  190 + </object>
  191 + <packing>
  192 + <property name="expand">False</property>
  193 + <property name="fill">True</property>
  194 + <property name="position">0</property>
  195 + </packing>
  196 + </child>
  197 + <child>
  198 + <object class="GtkNotebook" id="notebook1">
  199 + <property name="visible">True</property>
  200 + <property name="can_focus">True</property>
  201 + <child>
  202 + <object class="GtkScrolledWindow" id="scrolledwindow1">
  203 + <property name="visible">True</property>
  204 + <property name="can_focus">True</property>
  205 + <property name="shadow_type">in</property>
  206 + <child>
  207 + <object class="GtkTreeView" id="address_view">
  208 + <property name="visible">True</property>
  209 + <property name="can_focus">True</property>
  210 + <property name="enable_grid_lines">both</property>
  211 + <child internal-child="selection">
  212 + <object class="GtkTreeSelection" id="treeview-selection1"/>
  213 + </child>
  214 + </object>
  215 + </child>
  216 + </object>
  217 + <packing>
  218 + <property name="tab_expand">True</property>
  219 + </packing>
  220 + </child>
  221 + <child type="tab">
  222 + <object class="GtkLabel" id="notebook_label_addresses">
  223 + <property name="visible">True</property>
  224 + <property name="can_focus">False</property>
  225 + <property name="label" translatable="yes">Addresses</property>
  226 + </object>
  227 + <packing>
  228 + <property name="tab_fill">False</property>
  229 + </packing>
  230 + </child>
  231 + <child>
  232 + <object class="GtkScrolledWindow" id="scrolledwindow2">
  233 + <property name="visible">True</property>
  234 + <property name="can_focus">True</property>
  235 + <property name="shadow_type">in</property>
  236 + <child>
  237 + <object class="GtkTreeView" id="transactions_view">
  238 + <property name="visible">True</property>
  239 + <property name="can_focus">True</property>
  240 + <child internal-child="selection">
  241 + <object class="GtkTreeSelection" id="treeview-selection2"/>
  242 + </child>
  243 + </object>
  244 + </child>
  245 + </object>
  246 + <packing>
  247 + <property name="position">1</property>
  248 + <property name="tab_expand">True</property>
  249 + </packing>
  250 + </child>
  251 + <child type="tab">
  252 + <object class="GtkLabel" id="notebook_label_transactions">
  253 + <property name="visible">True</property>
  254 + <property name="can_focus">False</property>
  255 + <property name="label" translatable="yes">Transactions</property>
  256 + </object>
  257 + <packing>
  258 + <property name="position">1</property>
  259 + <property name="tab_fill">False</property>
  260 + </packing>
  261 + </child>
  262 + <child>
  263 + <object class="GtkScrolledWindow" id="scrolledwindow3">
  264 + <property name="visible">True</property>
  265 + <property name="can_focus">True</property>
  266 + <property name="shadow_type">in</property>
  267 + <child>
  268 + <object class="GtkTreeView" id="connections_view">
  269 + <property name="visible">True</property>
  270 + <property name="can_focus">True</property>
  271 + <child internal-child="selection">
  272 + <object class="GtkTreeSelection" id="treeview-selection4"/>
  273 + </child>
  274 + </object>
  275 + </child>
  276 + </object>
  277 + <packing>
  278 + <property name="position">2</property>
  279 + <property name="tab_expand">True</property>
  280 + </packing>
  281 + </child>
  282 + <child type="tab">
  283 + <object class="GtkLabel" id="notebook_label_peers">
  284 + <property name="visible">True</property>
  285 + <property name="can_focus">False</property>
  286 + <property name="label" translatable="yes">Connections</property>
  287 + </object>
  288 + <packing>
  289 + <property name="position">2</property>
  290 + <property name="tab_fill">False</property>
  291 + </packing>
  292 + </child>
  293 + </object>
  294 + <packing>
  295 + <property name="expand">True</property>
  296 + <property name="fill">True</property>
  297 + <property name="position">1</property>
  298 + </packing>
  299 + </child>
  300 + <child>
  301 + <object class="GtkButtonBox" id="buttonbox1">
  302 + <property name="visible">True</property>
  303 + <property name="can_focus">False</property>
  304 + <property name="homogeneous">True</property>
  305 + <property name="layout_style">start</property>
  306 + <child>
  307 + <object class="GtkButton" id="button_new_addr">
  308 + <property name="label" translatable="yes">New Address</property>
  309 + <property name="use_action_appearance">False</property>
  310 + <property name="visible">True</property>
  311 + <property name="can_focus">True</property>
  312 + <property name="receives_default">True</property>
  313 + <property name="use_action_appearance">False</property>
  314 + <property name="image">image_new_addr</property>
  315 + <signal name="clicked" handler="on_new_addr" swapped="no"/>
  316 + </object>
  317 + <packing>
  318 + <property name="expand">False</property>
  319 + <property name="fill">True</property>
  320 + <property name="position">0</property>
  321 + </packing>
  322 + </child>
  323 + <child>
  324 + <object class="GtkButton" id="button_new_tx">
  325 + <property name="label" translatable="yes">New Transaction</property>
  326 + <property name="use_action_appearance">False</property>
  327 + <property name="visible">True</property>
  328 + <property name="can_focus">True</property>
  329 + <property name="receives_default">True</property>
  330 + <property name="use_action_appearance">False</property>
  331 + <property name="image">image_new_tx</property>
  332 + </object>
  333 + <packing>
  334 + <property name="expand">False</property>
  335 + <property name="fill">True</property>
  336 + <property name="position">1</property>
  337 + </packing>
  338 + </child>
  339 + </object>
  340 + <packing>
  341 + <property name="expand">False</property>
  342 + <property name="fill">True</property>
  343 + <property name="position">2</property>
  344 + </packing>
  345 + </child>
  346 + <child>
  347 + <object class="GtkBox" id="box4">
  348 + <property name="visible">True</property>
  349 + <property name="can_focus">False</property>
  350 + <child>
  351 + <object class="GtkImage" id="image1">
  352 + <property name="visible">True</property>
  353 + <property name="can_focus">False</property>
  354 + <property name="stock">gtk-network</property>
  355 + </object>
  356 + <packing>
  357 + <property name="expand">False</property>
  358 + <property name="fill">True</property>
  359 + <property name="position">0</property>
  360 + </packing>
  361 + </child>
  362 + <child>
  363 + <object class="GtkStatusbar" id="status_network">
  364 + <property name="visible">True</property>
  365 + <property name="can_focus">False</property>
  366 + <property name="orientation">vertical</property>
  367 + <property name="spacing">2</property>
  368 + </object>
  369 + <packing>
  370 + <property name="expand">True</property>
  371 + <property name="fill">True</property>
  372 + <property name="padding">10</property>
  373 + <property name="position">1</property>
  374 + </packing>
  375 + </child>
  376 + <child>
  377 + <object class="GtkImage" id="image2">
  378 + <property name="visible">True</property>
  379 + <property name="can_focus">False</property>
  380 + <property name="stock">gtk-harddisk</property>
  381 + </object>
  382 + <packing>
  383 + <property name="expand">False</property>
  384 + <property name="fill">True</property>
  385 + <property name="position">2</property>
  386 + </packing>
  387 + </child>
  388 + <child>
  389 + <object class="GtkStatusbar" id="status_store">
  390 + <property name="visible">True</property>
  391 + <property name="can_focus">False</property>
  392 + <property name="orientation">vertical</property>
  393 + <property name="spacing">2</property>
  394 + </object>
  395 + <packing>
  396 + <property name="expand">True</property>
  397 + <property name="fill">True</property>
  398 + <property name="padding">10</property>
  399 + <property name="position">3</property>
  400 + </packing>
  401 + </child>
  402 + <child>
  403 + <object class="GtkImage" id="image3">
  404 + <property name="visible">True</property>
  405 + <property name="can_focus">False</property>
  406 + <property name="stock">gtk-dialog-authentication</property>
  407 + </object>
  408 + <packing>
  409 + <property name="expand">False</property>
  410 + <property name="fill">True</property>
  411 + <property name="position">4</property>
  412 + </packing>
  413 + </child>
  414 + <child>
  415 + <object class="GtkStatusbar" id="status_wallet">
  416 + <property name="visible">True</property>
  417 + <property name="can_focus">False</property>
  418 + <property name="orientation">vertical</property>
  419 + <property name="spacing">2</property>
  420 + </object>
  421 + <packing>
  422 + <property name="expand">True</property>
  423 + <property name="fill">True</property>
  424 + <property name="padding">10</property>
  425 + <property name="position">5</property>
  426 + </packing>
  427 + </child>
  428 + </object>
  429 + <packing>
  430 + <property name="expand">False</property>
  431 + <property name="fill">True</property>
  432 + <property name="position">3</property>
  433 + </packing>
  434 + </child>
  435 + </object>
  436 + </child>
  437 + </object>
  438 + <object class="GtkDialog" id="new_addr_dialog">
  439 + <property name="can_focus">False</property>
  440 + <property name="border_width">5</property>
  441 + <property name="type_hint">dialog</property>
  442 + <child internal-child="vbox">
  443 + <object class="GtkBox" id="dialog-vbox1">
  444 + <property name="can_focus">False</property>
  445 + <property name="orientation">vertical</property>
  446 + <property name="spacing">2</property>
  447 + <child internal-child="action_area">
  448 + <object class="GtkButtonBox" id="dialog-action_area1">
  449 + <property name="can_focus">False</property>
  450 + <property name="layout_style">end</property>
  451 + <child>
  452 + <object class="GtkButton" id="new_addr_dialog_cancel">
  453 + <property name="label">gtk-cancel</property>
  454 + <property name="use_action_appearance">False</property>
  455 + <property name="visible">True</property>
  456 + <property name="can_focus">True</property>
  457 + <property name="receives_default">True</property>
  458 + <property name="use_action_appearance">False</property>
  459 + <property name="use_stock">True</property>
  460 + <signal name="clicked" handler="on_new_addr_cancel" swapped="no"/>
  461 + </object>
  462 + <packing>
  463 + <property name="expand">False</property>
  464 + <property name="fill">True</property>
  465 + <property name="position">0</property>
  466 + </packing>
  467 + </child>
  468 + <child>
  469 + <object class="GtkButton" id="new_addr_dialog_apply">
  470 + <property name="label">gtk-apply</property>
  471 + <property name="use_action_appearance">False</property>
  472 + <property name="visible">True</property>
  473 + <property name="can_focus">True</property>
  474 + <property name="receives_default">True</property>
  475 + <property name="use_action_appearance">False</property>
  476 + <property name="use_stock">True</property>
  477 + <signal name="clicked" handler="on_new_addr_apply" swapped="no"/>
  478 + </object>
  479 + <packing>
  480 + <property name="expand">False</property>
  481 + <property name="fill">True</property>
  482 + <property name="position">1</property>
  483 + </packing>
  484 + </child>
  485 + </object>
  486 + <packing>
  487 + <property name="expand">False</property>
  488 + <property name="fill">True</property>
  489 + <property name="pack_type">end</property>
  490 + <property name="position">0</property>
  491 + </packing>
  492 + </child>
  493 + <child>
  494 + <object class="GtkGrid" id="grid1">
  495 + <property name="visible">True</property>
  496 + <property name="can_focus">False</property>
  497 + <child>
  498 + <object class="GtkLabel" id="label2">
  499 + <property name="visible">True</property>
  500 + <property name="can_focus">False</property>
  501 + <property name="margin_right">20</property>
  502 + <property name="label" translatable="yes">Label</property>
  503 + </object>
  504 + <packing>
  505 + <property name="left_attach">0</property>
  506 + <property name="top_attach">0</property>
  507 + <property name="width">1</property>
  508 + <property name="height">1</property>
  509 + </packing>
  510 + </child>
  511 + <child>
  512 + <object class="GtkEntry" id="new_addr_entry_label">
  513 + <property name="visible">True</property>
  514 + <property name="can_focus">True</property>
  515 + <property name="hexpand">True</property>
  516 + <property name="invisible_char">●</property>
  517 + <property name="width_chars">0</property>
  518 + <property name="shadow_type">none</property>
  519 + </object>
  520 + <packing>
  521 + <property name="left_attach">1</property>
  522 + <property name="top_attach">0</property>
  523 + <property name="width">1</property>
  524 + <property name="height">1</property>
  525 + </packing>
  526 + </child>
  527 + <child>
  528 + <placeholder/>
  529 + </child>
  530 + <child>
  531 + <placeholder/>
  532 + </child>
  533 + <child>
  534 + <placeholder/>
  535 + </child>
  536 + <child>
  537 + <placeholder/>
  538 + </child>
  539 + <child>
  540 + <placeholder/>
  541 + </child>
  542 + <child>
  543 + <placeholder/>
  544 + </child>
  545 + <child>
  546 + <placeholder/>
  547 + </child>
  548 + </object>
  549 + <packing>
  550 + <property name="expand">False</property>
  551 + <property name="fill">True</property>
  552 + <property name="position">1</property>
  553 + </packing>
  554 + </child>
  555 + </object>
  556 + </child>
  557 + <action-widgets>
  558 + <action-widget response="0">new_addr_dialog_cancel</action-widget>
  559 + <action-widget response="0">new_addr_dialog_apply</action-widget>
  560 + </action-widgets>
  561 + </object>
  562 +</interface>
lib/bitcoin/gui/gui.rb
  1 +require_relative "em_gtk.rb"
  2 +
  3 +module Bitcoin::Gui
  4 +
  5 + class Gui
  6 + attr_reader :builder
  7 + attr_accessor :node, :conn_view, :conn_store
  8 + def initialize storage, wallet_file = nil
  9 + @storage = storage
  10 + @node = nil
  11 + @builder = Gtk::Builder.new
  12 + @builder.add_from_file(File.join(File.dirname(__FILE__), "gui.builder"))
  13 + @builder.connect_signals {|handler| method(handler)}
  14 + @window = @builder.get_object("main_window")
  15 + setup_addr_view
  16 + setup_conn_view
  17 + open_wallet(wallet_file) if wallet_file
  18 + @window.show_all
  19 + end
  20 +
  21 + # VIEWS
  22 +
  23 + def setup_addr_view
  24 + @addr_view = @builder.get_object("address_view")
  25 + @addr_view.selection.mode = :multiple
  26 +
  27 + renderer = Gtk::CellRendererText.new
  28 + col = Gtk::TreeViewColumn.new("Address", renderer, :text => 0)
  29 + @addr_view.append_column(col)
  30 + col.set_cell_data_func(renderer) {|*a| format_address(*a) }
  31 +
  32 + renderer = Gtk::CellRendererText.new
  33 + col = Gtk::TreeViewColumn.new("Balance", renderer, :text => 1)
  34 + col.set_cell_data_func(renderer) {|*a| format_value(*a) }
  35 + @addr_view.append_column(col)
  36 +
  37 + @addr_store = Gtk::TreeStore.new(String, String, Fixnum)
  38 + end
  39 +
  40 + def setup_conn_view
  41 + @conn_view = @builder.get_object("connections_view")
  42 + renderer = Gtk::CellRendererText.new
  43 + %w[Host Port State Version Block Uptime UserAgent].each_with_index do |c, i|
  44 + @conn_view.append_column(Gtk::TreeViewColumn.new(c, renderer, :text => i))
  45 + end
  46 + @conn_view.columns[3].set_cell_data_func(renderer) {|*a| format_version(*a) }
  47 + @conn_view.columns[5].set_cell_data_func(renderer) {|*a| format_uptime(*a) }
  48 + @conn_store = Gtk::TreeStore.new(String, Fixnum, String, Fixnum, Fixnum, Fixnum, String)
  49 + end
  50 +
  51 + # STORES
  52 +
  53 + def update_addr_store
  54 + return unless @wallet
  55 + @addr_store.clear
  56 + @wallet.list.each do |addr, balance|
  57 + addr_row = @addr_store.append(nil)
  58 + addr_row[0] = addr[:addr]
  59 + addr_row[1] = addr[:label]
  60 + addr_row[2] = balance
  61 + @wallet.storage.get_txouts_for_address(addr[:addr]).each do |txout|
  62 + row = @addr_store.append(addr_row)
  63 + row[0] = txout.get_tx.hash
  64 + row[2] = txout.value
  65 + end
  66 + end
  67 + @addr_view.model = @addr_store
  68 + wallet_text = "wallet: #{@wallet.keystore.config[:file]} | " +
  69 + "addresses: #{@wallet.addrs.size} | " +
  70 + "balance: #{"%.8f" % (@wallet.get_balance / 1e8)}"
  71 + status_wallet.push 0, wallet_text
  72 + end
  73 +
  74 + # CALLBACKS
  75 +
  76 + def on_new_addr
  77 + new_addr_entry_label.text = ""
  78 + new_addr_dialog.show
  79 + end
  80 +
  81 + def on_new_addr_apply
  82 + @wallet.keystore.new_key(new_addr_entry_label.text)
  83 + update_addr_store
  84 + new_addr_dialog.hide
  85 + end
  86 +
  87 + def on_new_addr_cancel
  88 + @key = nil
  89 + new_addr_dialog.hide
  90 + end
  91 +
  92 + def on_new_wallet
  93 + wallet_dialog(:save, "File to save new wallet") do |file|
  94 + open_wallet(file)
  95 + end
  96 + end
  97 +
  98 + def on_open_wallet
  99 + wallet_dialog(:open, "Select wallet file to open") do |file|
  100 + open_wallet(file)
  101 + end
  102 + end
  103 +
  104 + def on_exit
  105 + Gtk.main_quit
  106 + EM.stop
  107 + end
  108 +
  109 + # HELPERS
  110 +
  111 + def open_wallet(file)
  112 + keystore = Bitcoin::Wallet::SimpleKeyStore.new(:file => file)
  113 + @wallet = Bitcoin::Wallet::Wallet.new(@storage, keystore, Bitcoin::Wallet::SimpleCoinSelector)
  114 + update_addr_store
  115 + end
  116 +
  117 + def wallet_dialog action, title = nil
  118 + dialog = Gtk::FileChooserDialog.new(title: title,
  119 + parent: @window, action: action, buttons: [
  120 + [ Gtk::Stock::CANCEL, :cancel],
  121 + [ Gtk::Stock::const_get(action.to_s.upcase), :accept],
  122 + ]
  123 + )
  124 +
  125 + filter = Gtk::FileFilter.new
  126 + filter.name = "Wallet Files"
  127 + filter.add_pattern("*.json")
  128 + dialog.add_filter filter
  129 +
  130 + filter = Gtk::FileFilter.new
  131 + filter.name = "All Files"
  132 + filter.add_pattern("*")
  133 + dialog.add_filter filter
  134 +
  135 + preview = Gtk::Label.new
  136 + dialog.preview_widget = preview
  137 + dialog.signal_connect("update-preview") do
  138 + begin
  139 + file = dialog.preview_filename
  140 + next unless file && File.file?(file)
  141 + keystore = Bitcoin::Wallet::SimpleKeyStore.new(:file => file)
  142 + wallet = Bitcoin::Wallet::Wallet.new(@storage, keystore, Bitcoin::Wallet::SimpleCoinSelector)
  143 + preview.text = "Keys: #{wallet.addrs.size}\nBalance:\n%.8f" % (wallet.get_balance / 1e8)
  144 + rescue
  145 + preview.text = "Not a wallet file"
  146 + end
  147 + end
  148 +
  149 + dialog.run do |res|
  150 + yield(dialog.filename) if res == Gtk::ResponseType::ACCEPT
  151 + dialog.destroy
  152 + end
  153 + end
  154 +
  155 + def format_value col, renderer, model, iter
  156 + val = iter[2].to_i
  157 + renderer.text = "%.8f" % (val / 1e8)
  158 + if val > 0
  159 + renderer.foreground = "darkgreen"
  160 + elsif val < 0
  161 + renderer.foreground = "red"
  162 + else
  163 + renderer.foreground = "black"
  164 + end
  165 + end
  166 +
  167 + def format_address col, renderer, model, iter
  168 + renderer.text = "#{iter[1]} (#{iter[0]})"
  169 + end
  170 +
  171 + def format_version col, renderer, model, iter
  172 + renderer.text = iter[3].to_s # TODO
  173 + end
  174 +
  175 + def format_uptime col, renderer, model, iter
  176 + uptime = Time.now.to_i - iter[5]
  177 + mm, ss = uptime.divmod(60) #=> [4515, 21]
  178 + hh, mm = mm.divmod(60) #=> [75, 15]
  179 + dd, hh = hh.divmod(24) #=> [3, 3]
  180 + renderer.text = "%02d:%02d:%02d:%02d" % [dd, hh, mm, ss]
  181 + end
  182 +
  183 + def method_missing name, *args
  184 + @builder.get_object(name.to_s) rescue super(name, *args)
  185 + end
  186 +
  187 + end
  188 +end
lib/bitcoin/network/command_handler.rb
1 1 require 'json'
  2 +require 'monitor'
2 3  
3 4 class Bitcoin::Network::CommandHandler < EM::Connection
4 5  
5 6 def initialize node
6 7 @node = node
7 8 @node.command_connections << self
8   - @notify_sid = nil
  9 + @buf = BufferedTokenizer.new("\x00")
  10 + @lock = Monitor.new
9 11 end
10 12  
11 13 def log
12 14  
13 15  
14 16  
15 17  
... ... @@ -13,32 +15,42 @@
13 15 end
14 16  
15 17 def respond(cmd, data)
16   - send_data([cmd, data].to_json + "\x00")
  18 + @lock.synchronize do
  19 + send_data([cmd, data].to_json + "\x00")
  20 + end
17 21 end
18 22  
19   - def receive_data line
20   - cmd, args = JSON::parse(line)
21   - *args = args.split(" ")
22   - log.debug { line.chomp }
23   - if respond_to?("handle_#{cmd}")
24   - respond(cmd, send("handle_#{cmd}", *args))
25   - else
26   - respond(cmd, {:error => "unknown command: #{cmd}. send 'help' for help."})
  23 + def receive_data data
  24 + @buf.extract(data).each do |packet|
  25 + p packet
  26 + cmd, args = JSON::parse(packet)
  27 + *args = args.split(" ")
  28 + log.debug { line.chomp }
  29 + if respond_to?("handle_#{cmd}")
  30 + respond(cmd, send("handle_#{cmd}", *args))
  31 + else
  32 + respond(cmd, {:error => "unknown command: #{cmd}. send 'help' for help."})
  33 + end
27 34 end
28 35 rescue Exception
29 36 p $!
30 37 end
31 38  
32   - def handle_monitor
33   - @node.notify.subscribe do |type, obj, depth|
34   - if type.to_sym == :block
35   - respond("monitor", [type.to_s, obj, depth])
36   - else
37   - respond("monitor", [type.to_s, obj])
  39 + def handle_monitor *channels
  40 + channels.each do |channel|
  41 + @node.notifiers[channel.to_sym].subscribe do |*data|
  42 + respond("monitor", [channel, *data])
38 43 end
  44 + case channel.to_sym
  45 + when :block
  46 + head = Bitcoin::P::Block.new(@node.store.get_head.to_payload) rescue nil
  47 + respond("monitor", ["block", head, @node.store.get_depth.to_s])
  48 + when :connection
  49 + @node.connections.select {|c| c.connected?}.each do |conn|
  50 + respond("monitor", [:connection, [:connected, conn.info]])
  51 + end
  52 + end
39 53 end
40