From ee84b202473a5b629a3a2b43154edb989629ddfc Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 8 Jan 2015 16:38:24 +0100 Subject: [PATCH] Reworking GUI interaction. Fixed javascript inject. Closes #132 --- cmd/mist/assets/qml/browser.qml | 12 +- cmd/mist/assets/qml/main.qml | 4 +- cmd/mist/gui.go | 242 +++++++++++++++++++------------- 3 files changed, 158 insertions(+), 100 deletions(-) diff --git a/cmd/mist/assets/qml/browser.qml b/cmd/mist/assets/qml/browser.qml index abaab4f15c..4cf6b24704 100644 --- a/cmd/mist/assets/qml/browser.qml +++ b/cmd/mist/assets/qml/browser.qml @@ -59,7 +59,7 @@ Rectangle { } Component.onCompleted: { - webview.url = "http://etherian.io" + webview.url = "/Users/jeffrey/test.html" } signal messages(var messages, int id); @@ -109,7 +109,8 @@ Rectangle { leftMargin: 5 rightMargin: 5 } - text: "http://etherian.io" + //text: "http://etherian.io" + text: webview.url; id: uriNav y: parent.height / 2 - this.height / 2 @@ -151,6 +152,10 @@ Rectangle { window.open(request.url.toString()); } + function injectJs(js) { + experimental.evaluateJavaScript(js) + } + function sendMessage(data) { webview.experimental.postMessage(JSON.stringify(data)) } @@ -159,8 +164,7 @@ Rectangle { experimental.preferences.javascriptEnabled: true experimental.preferences.navigatorQtObjectEnabled: true experimental.preferences.developerExtrasEnabled: true - //experimental.userScripts: ["../ext/qt_messaging_adapter.js", "../ext/q.js", "../ext/big.js", "../ext/string.js", "../ext/html_messaging.js"] - experimental.userScripts: ["../ext/q.js", "../ext/eth.js/main.js", "../ext/eth.js/qt.js", "../ext/setup.js"] + //experimental.userScripts: ["../ext/q.js", "../ext/eth.js/main.js", "../ext/eth.js/qt.js", "../ext/setup.js"] experimental.onMessageReceived: { console.log("[onMessageReceived]: ", message.data) // TODO move to messaging.js diff --git a/cmd/mist/assets/qml/main.qml b/cmd/mist/assets/qml/main.qml index 111bef8bb4..e287e384f2 100644 --- a/cmd/mist/assets/qml/main.qml +++ b/cmd/mist/assets/qml/main.qml @@ -59,8 +59,8 @@ ApplicationWindow { mainSplit.setView(wallet.view, wallet.menuItem); - // Call the ready handler - gui.done(); + // Command setup + gui.sendCommand(0) } function addViews(view, path, options) { diff --git a/cmd/mist/gui.go b/cmd/mist/gui.go index 2e3f329b2a..083fd5d0a3 100644 --- a/cmd/mist/gui.go +++ b/cmd/mist/gui.go @@ -26,7 +26,9 @@ import ( "bytes" "encoding/json" "fmt" + "io/ioutil" "math/big" + "os" "path" "runtime" "strconv" @@ -48,15 +50,22 @@ import ( var guilogger = logger.NewLogger("GUI") +type ServEv byte + +const ( + setup ServEv = iota + update +) + type Gui struct { // The main application window win *qml.Window // QML Engine engine *qml.Engine component *qml.Common - qmlDone bool // The ethereum interface - eth *eth.Ethereum + eth *eth.Ethereum + serviceEvents chan ServEv // The public Ethereum library uiLib *UiLib @@ -86,7 +95,17 @@ func NewWindow(ethereum *eth.Ethereum, config *ethutil.ConfigManager, clientIden } xeth := xeth.NewJSXEth(ethereum) - gui := &Gui{eth: ethereum, txDb: db, xeth: xeth, logLevel: logger.LogLevel(logLevel), Session: session, open: false, clientIdentity: clientIdentity, config: config, plugins: make(map[string]plugin)} + gui := &Gui{eth: ethereum, + txDb: db, + xeth: xeth, + logLevel: logger.LogLevel(logLevel), + Session: session, + open: false, + clientIdentity: clientIdentity, + config: config, + plugins: make(map[string]plugin), + serviceEvents: make(chan ServEv, 1), + } data, _ := ethutil.ReadAllFile(path.Join(ethutil.Config.ExecPath, "plugins.json")) json.Unmarshal([]byte(data), &gui.plugins) @@ -98,6 +117,8 @@ func (gui *Gui) Start(assetPath string) { guilogger.Infoln("Starting GUI") + go gui.service() + // Register ethereum functions qml.RegisterTypes("Ethereum", 1, 0, []qml.TypeSpec{{ Init: func(p *xeth.JSBlock, obj qml.Object) { p.Number = 0; p.Hash = "" }, @@ -154,18 +175,11 @@ func (gui *Gui) showWallet(context *qml.Context) (*qml.Window, error) { return nil, err } - gui.win = gui.createWindow(component) - - gui.update() + gui.createWindow(component) return gui.win, nil } -// The done handler will be called by QML when all views have been loaded -func (gui *Gui) Done() { - gui.qmlDone = true -} - func (gui *Gui) ImportKey(filePath string) { } @@ -179,10 +193,8 @@ func (gui *Gui) showKeyImport(context *qml.Context) (*qml.Window, error) { } func (gui *Gui) createWindow(comp qml.Object) *qml.Window { - win := comp.CreateWindow(nil) - - gui.win = win - gui.uiLib.win = win + gui.win = comp.CreateWindow(nil) + gui.uiLib.win = gui.win return gui.win } @@ -335,11 +347,48 @@ func (self *Gui) getObjectByName(objectName string) qml.Object { return self.win.Root().ObjectByName(objectName) } -// Simple go routine function that updates the list of peers in the GUI -func (gui *Gui) update() { - // We have to wait for qml to be done loading all the windows. - for !gui.qmlDone { - time.Sleep(300 * time.Millisecond) +func loadJavascriptAssets(gui *Gui) (jsfiles string) { + for _, fn := range []string{"ext/q.js", "ext/eth.js/main.js", "ext/eth.js/qt.js", "ext/setup.js"} { + f, err := os.Open(gui.uiLib.AssetPath(fn)) + if err != nil { + fmt.Println(err) + continue + } + + content, err := ioutil.ReadAll(f) + if err != nil { + fmt.Println(err) + continue + } + jsfiles += string(content) + } + + return +} + +func (gui *Gui) SendCommand(cmd ServEv) { + gui.serviceEvents <- cmd +} + +func (gui *Gui) service() { + for ev := range gui.serviceEvents { + switch ev { + case setup: + go gui.setup() + case update: + go gui.update() + } + } +} + +func (gui *Gui) setup() { + for gui.win == nil { + time.Sleep(time.Millisecond * 200) + } + + for _, plugin := range gui.plugins { + guilogger.Infoln("Loading plugin ", plugin.Name) + gui.win.Root().Call("addPlugin", plugin.Path, "") } go func() { @@ -349,14 +398,21 @@ func (gui *Gui) update() { gui.setPeerInfo() }() - gui.whisper.SetView(gui.win.Root().ObjectByName("whisperView")) + // Inject javascript files each time navigation is requested. + // Unfortunately webview.experimental.userScripts injects _after_ + // the page has loaded which kind of renders it useless... + jsfiles := loadJavascriptAssets(gui) + gui.getObjectByName("webView").On("navigationRequested", func() { + gui.getObjectByName("webView").Call("injectJs", jsfiles) + }) - for _, plugin := range gui.plugins { - guilogger.Infoln("Loading plugin ", plugin.Name) + gui.whisper.SetView(gui.getObjectByName("whisperView")) - gui.win.Root().Call("addPlugin", plugin.Path, "") - } + gui.SendCommand(update) +} +// Simple go routine function that updates the list of peers in the GUI +func (gui *Gui) update() { peerUpdateTicker := time.NewTicker(5 * time.Second) generalUpdateTicker := time.NewTicker(500 * time.Millisecond) statsUpdateTicker := time.NewTicker(5 * time.Second) @@ -375,77 +431,75 @@ func (gui *Gui) update() { core.TxPostEvent{}, ) - go func() { - defer events.Unsubscribe() - for { - select { - case ev, isopen := <-events.Chan(): - if !isopen { - return - } - switch ev := ev.(type) { - case core.NewBlockEvent: - gui.processBlock(ev.Block, false) - if bytes.Compare(ev.Block.Coinbase(), gui.address()) == 0 { - gui.setWalletValue(gui.eth.ChainManager().State().GetBalance(gui.address()), nil) - } - - case core.TxPreEvent: - tx := ev.Tx - - tstate := gui.eth.ChainManager().TransState() - cstate := gui.eth.ChainManager().State() - - taccount := tstate.GetAccount(gui.address()) - caccount := cstate.GetAccount(gui.address()) - unconfirmedFunds := new(big.Int).Sub(taccount.Balance(), caccount.Balance()) - - gui.setWalletValue(taccount.Balance(), unconfirmedFunds) - gui.insertTransaction("pre", tx) - - case core.TxPostEvent: - tx := ev.Tx - object := state.GetAccount(gui.address()) - - if bytes.Compare(tx.From(), gui.address()) == 0 { - object.SubAmount(tx.Value()) - - gui.txDb.Put(tx.Hash(), tx.RlpEncode()) - } else if bytes.Compare(tx.To(), gui.address()) == 0 { - object.AddAmount(tx.Value()) - - gui.txDb.Put(tx.Hash(), tx.RlpEncode()) - } - - gui.setWalletValue(object.Balance(), nil) - state.UpdateStateObject(object) - } - - case <-peerUpdateTicker.C: - gui.setPeerInfo() - case <-generalUpdateTicker.C: - statusText := "#" + gui.eth.ChainManager().CurrentBlock().Number().String() - lastBlockLabel.Set("text", statusText) - miningLabel.Set("text", "Mining @ "+strconv.FormatInt(gui.uiLib.miner.GetPow().GetHashrate(), 10)+"Khash") - - /* - blockLength := gui.eth.BlockPool().BlocksProcessed - chainLength := gui.eth.BlockPool().ChainLength - - var ( - pct float64 = 1.0 / float64(chainLength) * float64(blockLength) - dlWidget = gui.win.Root().ObjectByName("downloadIndicator") - dlLabel = gui.win.Root().ObjectByName("downloadLabel") - ) - dlWidget.Set("value", pct) - dlLabel.Set("text", fmt.Sprintf("%d / %d", blockLength, chainLength)) - */ - - case <-statsUpdateTicker.C: - gui.setStatsPane() + defer events.Unsubscribe() + for { + select { + case ev, isopen := <-events.Chan(): + if !isopen { + return } + switch ev := ev.(type) { + case core.NewBlockEvent: + gui.processBlock(ev.Block, false) + if bytes.Compare(ev.Block.Coinbase(), gui.address()) == 0 { + gui.setWalletValue(gui.eth.ChainManager().State().GetBalance(gui.address()), nil) + } + + case core.TxPreEvent: + tx := ev.Tx + + tstate := gui.eth.ChainManager().TransState() + cstate := gui.eth.ChainManager().State() + + taccount := tstate.GetAccount(gui.address()) + caccount := cstate.GetAccount(gui.address()) + unconfirmedFunds := new(big.Int).Sub(taccount.Balance(), caccount.Balance()) + + gui.setWalletValue(taccount.Balance(), unconfirmedFunds) + gui.insertTransaction("pre", tx) + + case core.TxPostEvent: + tx := ev.Tx + object := state.GetAccount(gui.address()) + + if bytes.Compare(tx.From(), gui.address()) == 0 { + object.SubAmount(tx.Value()) + + gui.txDb.Put(tx.Hash(), tx.RlpEncode()) + } else if bytes.Compare(tx.To(), gui.address()) == 0 { + object.AddAmount(tx.Value()) + + gui.txDb.Put(tx.Hash(), tx.RlpEncode()) + } + + gui.setWalletValue(object.Balance(), nil) + state.UpdateStateObject(object) + } + + case <-peerUpdateTicker.C: + gui.setPeerInfo() + case <-generalUpdateTicker.C: + statusText := "#" + gui.eth.ChainManager().CurrentBlock().Number().String() + lastBlockLabel.Set("text", statusText) + miningLabel.Set("text", "Mining @ "+strconv.FormatInt(gui.uiLib.miner.GetPow().GetHashrate(), 10)+"Khash") + + /* + blockLength := gui.eth.BlockPool().BlocksProcessed + chainLength := gui.eth.BlockPool().ChainLength + + var ( + pct float64 = 1.0 / float64(chainLength) * float64(blockLength) + dlWidget = gui.win.Root().ObjectByName("downloadIndicator") + dlLabel = gui.win.Root().ObjectByName("downloadLabel") + ) + dlWidget.Set("value", pct) + dlLabel.Set("text", fmt.Sprintf("%d / %d", blockLength, chainLength)) + */ + + case <-statsUpdateTicker.C: + gui.setStatsPane() } - }() + } } func (gui *Gui) setStatsPane() {