gui.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. /*
  2. This file is part of go-ethereum
  3. go-ethereum is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation, either version 3 of the License, or
  6. (at your option) any later version.
  7. go-ethereum is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
  13. */
  14. /**
  15. * @authors
  16. * Jeffrey Wilcke <i@jev.io>
  17. */
  18. package main
  19. import "C"
  20. import (
  21. "encoding/json"
  22. "fmt"
  23. "io/ioutil"
  24. "math/big"
  25. "path/filepath"
  26. "runtime"
  27. "sort"
  28. "time"
  29. "github.com/ethereum/go-ethereum/common"
  30. "github.com/ethereum/go-ethereum/core"
  31. "github.com/ethereum/go-ethereum/core/types"
  32. "github.com/ethereum/go-ethereum/eth"
  33. "github.com/ethereum/go-ethereum/ethdb"
  34. "github.com/ethereum/go-ethereum/logger"
  35. "github.com/ethereum/go-ethereum/ui/qt/qwhisper"
  36. "github.com/ethereum/go-ethereum/xeth"
  37. "github.com/obscuren/qml"
  38. )
  39. var guilogger = logger.NewLogger("GUI")
  40. type ServEv byte
  41. const (
  42. setup ServEv = iota
  43. update
  44. )
  45. type Gui struct {
  46. // The main application window
  47. win *qml.Window
  48. // QML Engine
  49. engine *qml.Engine
  50. component *qml.Common
  51. // The ethereum interface
  52. eth *eth.Ethereum
  53. serviceEvents chan ServEv
  54. // The public Ethereum library
  55. uiLib *UiLib
  56. whisper *qwhisper.Whisper
  57. txDb *ethdb.LDBDatabase
  58. open bool
  59. xeth *xeth.XEth
  60. Session string
  61. plugins map[string]plugin
  62. }
  63. // Create GUI, but doesn't start it
  64. func NewWindow(ethereum *eth.Ethereum) *Gui {
  65. db, err := ethdb.NewLDBDatabase(filepath.Join(ethereum.DataDir, "tx_database"))
  66. if err != nil {
  67. panic(err)
  68. }
  69. xeth := xeth.New(ethereum, nil)
  70. gui := &Gui{eth: ethereum,
  71. txDb: db,
  72. xeth: xeth,
  73. open: false,
  74. plugins: make(map[string]plugin),
  75. serviceEvents: make(chan ServEv, 1),
  76. }
  77. data, _ := ioutil.ReadFile(filepath.Join(ethereum.DataDir, "plugins.json"))
  78. json.Unmarshal(data, &gui.plugins)
  79. return gui
  80. }
  81. func (gui *Gui) Start(assetPath, libPath string) {
  82. defer gui.txDb.Close()
  83. guilogger.Infoln("Starting GUI")
  84. go gui.service()
  85. // Register ethereum functions
  86. qml.RegisterTypes("Ethereum", 1, 0, []qml.TypeSpec{{
  87. Init: func(p *xeth.Block, obj qml.Object) { p.Number = 0; p.Hash = "" },
  88. }, {
  89. Init: func(p *xeth.Transaction, obj qml.Object) { p.Value = ""; p.Hash = ""; p.Address = "" },
  90. }, {
  91. Init: func(p *xeth.KeyVal, obj qml.Object) { p.Key = ""; p.Value = "" },
  92. }})
  93. // Create a new QML engine
  94. gui.engine = qml.NewEngine()
  95. context := gui.engine.Context()
  96. gui.uiLib = NewUiLib(gui.engine, gui.eth, assetPath, libPath)
  97. gui.whisper = qwhisper.New(gui.eth.Whisper())
  98. // Expose the eth library and the ui library to QML
  99. context.SetVar("gui", gui)
  100. context.SetVar("eth", gui.uiLib)
  101. context.SetVar("shh", gui.whisper)
  102. //clipboard.SetQMLClipboard(context)
  103. win, err := gui.showWallet(context)
  104. if err != nil {
  105. guilogger.Errorln("asset not found: you can set an alternative asset path on the command line using option 'asset_path'", err)
  106. panic(err)
  107. }
  108. gui.open = true
  109. win.Show()
  110. win.Wait()
  111. gui.open = false
  112. }
  113. func (gui *Gui) Stop() {
  114. if gui.open {
  115. gui.open = false
  116. gui.win.Hide()
  117. }
  118. guilogger.Infoln("Stopped")
  119. }
  120. func (gui *Gui) showWallet(context *qml.Context) (*qml.Window, error) {
  121. component, err := gui.engine.LoadFile(gui.uiLib.AssetPath("qml/main.qml"))
  122. if err != nil {
  123. return nil, err
  124. }
  125. gui.createWindow(component)
  126. return gui.win, nil
  127. }
  128. func (gui *Gui) GenerateKey() {
  129. _, err := gui.eth.AccountManager().NewAccount("hurr")
  130. if err != nil {
  131. // TODO: UI feedback?
  132. }
  133. }
  134. func (gui *Gui) showKeyImport(context *qml.Context) (*qml.Window, error) {
  135. context.SetVar("lib", gui)
  136. component, err := gui.engine.LoadFile(gui.uiLib.AssetPath("qml/first_run.qml"))
  137. if err != nil {
  138. return nil, err
  139. }
  140. return gui.createWindow(component), nil
  141. }
  142. func (gui *Gui) createWindow(comp qml.Object) *qml.Window {
  143. gui.win = comp.CreateWindow(nil)
  144. gui.uiLib.win = gui.win
  145. return gui.win
  146. }
  147. func (gui *Gui) setInitialChain(ancientBlocks bool) {
  148. sBlk := gui.eth.ChainManager().LastBlockHash()
  149. blk := gui.eth.ChainManager().GetBlock(sBlk)
  150. for ; blk != nil; blk = gui.eth.ChainManager().GetBlock(sBlk) {
  151. sBlk = blk.ParentHash()
  152. gui.processBlock(blk, true)
  153. }
  154. }
  155. func (gui *Gui) loadAddressBook() {
  156. /*
  157. view := gui.getObjectByName("infoView")
  158. nameReg := gui.xeth.World().Config().Get("NameReg")
  159. if nameReg != nil {
  160. it := nameReg.Trie().Iterator()
  161. for it.Next() {
  162. if it.Key[0] != 0 {
  163. view.Call("addAddress", struct{ Name, Address string }{string(it.Key), common.Bytes2Hex(it.Value)})
  164. }
  165. }
  166. }
  167. */
  168. }
  169. func (self *Gui) loadMergedMiningOptions() {
  170. /*
  171. view := self.getObjectByName("mergedMiningModel")
  172. mergeMining := self.xeth.World().Config().Get("MergeMining")
  173. if mergeMining != nil {
  174. i := 0
  175. it := mergeMining.Trie().Iterator()
  176. for it.Next() {
  177. view.Call("addMergedMiningOption", struct {
  178. Checked bool
  179. Name, Address string
  180. Id, ItemId int
  181. }{false, string(it.Key), common.Bytes2Hex(it.Value), 0, i})
  182. i++
  183. }
  184. }
  185. */
  186. }
  187. func (gui *Gui) insertTransaction(window string, tx *types.Transaction) {
  188. var inout string
  189. from, _ := tx.From()
  190. if gui.eth.AccountManager().HasAccount(common.Hex2Bytes(from.Hex())) {
  191. inout = "send"
  192. } else {
  193. inout = "recv"
  194. }
  195. ptx := xeth.NewTx(tx)
  196. ptx.Sender = from.Hex()
  197. if to := tx.To(); to != nil {
  198. ptx.Address = to.Hex()
  199. }
  200. if window == "post" {
  201. //gui.getObjectByName("transactionView").Call("addTx", ptx, inout)
  202. } else {
  203. gui.getObjectByName("pendingTxView").Call("addTx", ptx, inout)
  204. }
  205. }
  206. func (gui *Gui) readPreviousTransactions() {
  207. it := gui.txDb.NewIterator()
  208. for it.Next() {
  209. tx := types.NewTransactionFromBytes(it.Value())
  210. gui.insertTransaction("post", tx)
  211. }
  212. it.Release()
  213. }
  214. func (gui *Gui) processBlock(block *types.Block, initial bool) {
  215. name := block.Coinbase().Hex()
  216. b := xeth.NewBlock(block)
  217. b.Name = name
  218. gui.getObjectByName("chainView").Call("addBlock", b, initial)
  219. }
  220. func (gui *Gui) setWalletValue(amount, unconfirmedFunds *big.Int) {
  221. var str string
  222. if unconfirmedFunds != nil {
  223. pos := "+"
  224. if unconfirmedFunds.Cmp(big.NewInt(0)) < 0 {
  225. pos = "-"
  226. }
  227. val := common.CurrencyToString(new(big.Int).Abs(common.BigCopy(unconfirmedFunds)))
  228. str = fmt.Sprintf("%v (%s %v)", common.CurrencyToString(amount), pos, val)
  229. } else {
  230. str = fmt.Sprintf("%v", common.CurrencyToString(amount))
  231. }
  232. gui.win.Root().Call("setWalletValue", str)
  233. }
  234. func (self *Gui) getObjectByName(objectName string) qml.Object {
  235. return self.win.Root().ObjectByName(objectName)
  236. }
  237. func (gui *Gui) SendCommand(cmd ServEv) {
  238. gui.serviceEvents <- cmd
  239. }
  240. func (gui *Gui) service() {
  241. for ev := range gui.serviceEvents {
  242. switch ev {
  243. case setup:
  244. go gui.setup()
  245. case update:
  246. go gui.update()
  247. }
  248. }
  249. }
  250. func (gui *Gui) setup() {
  251. for gui.win == nil {
  252. time.Sleep(time.Millisecond * 200)
  253. }
  254. for _, plugin := range gui.plugins {
  255. guilogger.Infoln("Loading plugin ", plugin.Name)
  256. gui.win.Root().Call("addPlugin", plugin.Path, "")
  257. }
  258. go func() {
  259. go gui.setInitialChain(false)
  260. gui.loadAddressBook()
  261. gui.loadMergedMiningOptions()
  262. gui.setPeerInfo()
  263. }()
  264. gui.whisper.SetView(gui.getObjectByName("whisperView"))
  265. gui.SendCommand(update)
  266. }
  267. // Simple go routine function that updates the list of peers in the GUI
  268. func (gui *Gui) update() {
  269. peerUpdateTicker := time.NewTicker(5 * time.Second)
  270. generalUpdateTicker := time.NewTicker(500 * time.Millisecond)
  271. statsUpdateTicker := time.NewTicker(5 * time.Second)
  272. lastBlockLabel := gui.getObjectByName("lastBlockLabel")
  273. //miningLabel := gui.getObjectByName("miningLabel")
  274. events := gui.eth.EventMux().Subscribe(
  275. core.ChainEvent{},
  276. core.TxPreEvent{},
  277. core.TxPostEvent{},
  278. )
  279. defer events.Unsubscribe()
  280. for {
  281. select {
  282. case ev, isopen := <-events.Chan():
  283. if !isopen {
  284. return
  285. }
  286. switch ev := ev.(type) {
  287. case core.ChainEvent:
  288. gui.processBlock(ev.Block, false)
  289. case core.TxPreEvent:
  290. gui.insertTransaction("pre", ev.Tx)
  291. case core.TxPostEvent:
  292. gui.getObjectByName("pendingTxView").Call("removeTx", xeth.NewTx(ev.Tx))
  293. }
  294. case <-peerUpdateTicker.C:
  295. gui.setPeerInfo()
  296. case <-generalUpdateTicker.C:
  297. statusText := "#" + gui.eth.ChainManager().CurrentBlock().Number().String()
  298. lastBlockLabel.Set("text", statusText)
  299. //miningLabel.Set("text", strconv.FormatInt(gui.uiLib.Miner().HashRate(), 10))
  300. case <-statsUpdateTicker.C:
  301. gui.setStatsPane()
  302. }
  303. }
  304. }
  305. func (gui *Gui) setStatsPane() {
  306. var memStats runtime.MemStats
  307. runtime.ReadMemStats(&memStats)
  308. statsPane := gui.getObjectByName("statsPane")
  309. statsPane.Set("text", fmt.Sprintf(`###### Mist %s (%s) #######
  310. eth %d (p2p = %d)
  311. CPU: # %d
  312. Goroutines: # %d
  313. CGoCalls: # %d
  314. Alloc: %d
  315. Heap Alloc: %d
  316. CGNext: %x
  317. NumGC: %d
  318. `, Version, runtime.Version(),
  319. eth.ProtocolVersion, 2,
  320. runtime.NumCPU, runtime.NumGoroutine(), runtime.NumCgoCall(),
  321. memStats.Alloc, memStats.HeapAlloc,
  322. memStats.NextGC, memStats.NumGC,
  323. ))
  324. }
  325. type qmlpeer struct{ Addr, NodeID, Name, Caps string }
  326. type peersByID []*qmlpeer
  327. func (s peersByID) Len() int { return len(s) }
  328. func (s peersByID) Less(i, j int) bool { return s[i].NodeID < s[j].NodeID }
  329. func (s peersByID) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
  330. func (gui *Gui) setPeerInfo() {
  331. peers := gui.eth.Peers()
  332. qpeers := make(peersByID, len(peers))
  333. for i, p := range peers {
  334. qpeers[i] = &qmlpeer{
  335. NodeID: p.ID().String(),
  336. Addr: p.RemoteAddr().String(),
  337. Name: p.Name(),
  338. Caps: fmt.Sprint(p.Caps()),
  339. }
  340. }
  341. // we need to sort the peers because they jump around randomly
  342. // otherwise. order returned by eth.Peers is random because they
  343. // are taken from a map.
  344. sort.Sort(qpeers)
  345. gui.win.Root().Call("setPeerCounters", fmt.Sprintf("%d / %d", len(peers), gui.eth.MaxPeers()))
  346. gui.win.Root().Call("clearPeers")
  347. for _, p := range qpeers {
  348. gui.win.Root().Call("addPeer", p)
  349. }
  350. }
  351. /*
  352. func LoadExtension(path string) (uintptr, error) {
  353. lib, err := ffi.NewLibrary(path)
  354. if err != nil {
  355. return 0, err
  356. }
  357. so, err := lib.Fct("sharedObject", ffi.Pointer, nil)
  358. if err != nil {
  359. return 0, err
  360. }
  361. ptr := so()
  362. err = lib.Close()
  363. if err != nil {
  364. return 0, err
  365. }
  366. return ptr.Interface().(uintptr), nil
  367. }
  368. */
  369. /*
  370. vec, errr := LoadExtension("/Users/jeffrey/Desktop/build-libqmltest-Desktop_Qt_5_2_1_clang_64bit-Debug/liblibqmltest_debug.dylib")
  371. fmt.Printf("Fetched vec with addr: %#x\n", vec)
  372. if errr != nil {
  373. fmt.Println(errr)
  374. } else {
  375. context.SetVar("vec", (unsafe.Pointer)(vec))
  376. }
  377. */