├── .gitignore ├── spec ├── page1.html ├── support │ └── jasmine.json ├── app-spec.go ├── webContents-spec.go └── browserWindow-spec.go ├── scripts ├── test.go └── test.js ├── emitter.go ├── .drone.yml ├── package.json ├── .travis.yml ├── app.go ├── README.md ├── browserWindow.go ├── webContents.go └── electron.go /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | spec/*.js.map 3 | spec/*.js 4 | ./*.js 5 | ./*.js.map 6 | *.log 7 | -------------------------------------------------------------------------------- /spec/page1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Page1 4 | 111 5 | 6 | 7 | -------------------------------------------------------------------------------- /scripts/test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import _ "github.com/arvitaly/gopherjs-electron/spec" 4 | 5 | func main() { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /emitter.go: -------------------------------------------------------------------------------- 1 | package electron 2 | 3 | type EventEmitter interface { 4 | On(Event string, listener func(args ...interface{})) 5 | } 6 | -------------------------------------------------------------------------------- /spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": [ 4 | "**/*[sS]pec.js" 5 | ], 6 | "helpers": [ 7 | "helpers/**/*.js" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | console.log("Start jasmine tests") 2 | 3 | var Jasmine = require('jasmine'); 4 | var jasmine = new Jasmine(); 5 | 6 | jasmine.loadConfigFile('spec/support/jasmine.json'); 7 | 8 | jasmine.execute(); 9 | -------------------------------------------------------------------------------- /.drone.yml: -------------------------------------------------------------------------------- 1 | build: 2 | image: arvitaly/electron-go:latest 3 | commands: 4 | - go get 5 | - go build 6 | - go test 7 | - go get github.com/arvitaly/gopherjs-jasmine 8 | - npm install 9 | - export DISPLAY=':99.0' 10 | - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & 11 | - npm test -- 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gopherjs-electron", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "main.js", 6 | "scripts": { 7 | "start": "electron main.js", 8 | "test": "gopherjs build ./scripts/test.go -o ./spec/all-spec.js && electron scripts/test.js" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "jasmine": "^2.4.1" 14 | }, 15 | "dependencies": {} 16 | } 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.5 4 | node_js: 5 | - "0.12.7" 6 | sudo: false 7 | addons: 8 | apt: 9 | packages: 10 | - xvfb 11 | install: 12 | - . $HOME/.nvm/nvm.sh 13 | - nvm install stable 14 | - nvm use stable 15 | - npm install 16 | - npm install electron-prebuilt -g 17 | - go get github.com/arvitaly/gopherjs-jasmine 18 | - go get -u github.com/gopherjs/gopherjs 19 | - export DISPLAY=':99.0' 20 | - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & 21 | script: 22 | - npm test 23 | -------------------------------------------------------------------------------- /app.go: -------------------------------------------------------------------------------- 1 | package electron 2 | 3 | import "github.com/gopherjs/gopherjs/js" 4 | 5 | type App interface { 6 | OnWillQuit(listener func(event *js.Object)) 7 | OnReady(listener func()) 8 | GetAppPath() string 9 | } 10 | 11 | type _App struct { 12 | *js.Object 13 | } 14 | 15 | func (a *_App) OnReady(listener func()) { 16 | a.Call("on", "ready", func() { 17 | listener() 18 | }) 19 | } 20 | func (a *_App) OnWillQuit(listener func(event *js.Object)) { 21 | a.Call("on", "will-quit", func(event *js.Object) { 22 | listener(event) 23 | }) 24 | } 25 | 26 | func (a *_App) GetAppPath() string { 27 | return a.Call("getAppPath").String() 28 | } 29 | -------------------------------------------------------------------------------- /spec/app-spec.go: -------------------------------------------------------------------------------- 1 | package spec 2 | 3 | import "github.com/arvitaly/gopherjs-electron" 4 | import "github.com/gopherjs/gopherjs/js" 5 | import "github.com/arvitaly/gopherjs-jasmine" 6 | 7 | var _ = jasmine.Run(func() { 8 | jasmine.Describe("App", func() { 9 | var app = electron.GetApp() 10 | jasmine.ItAsync("OnReady", func(done func()) { 11 | app.OnReady(func() { 12 | done() 13 | }) 14 | }) 15 | jasmine.It("GetAppPath", func() { 16 | jasmine.Expect(app.GetAppPath() != "").ToBeTruthy() 17 | }) 18 | jasmine.ItAsync("OnWillQuit", func(done func()) { 19 | var firstTime = true 20 | app.OnWillQuit(func(event *js.Object) { 21 | if firstTime { 22 | event.Call("preventDefault") 23 | } 24 | firstTime = false 25 | done() 26 | }) 27 | var br = electron.NewBrowserWindow(nil) 28 | br.Close() 29 | }) 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gopherjs-electron 2 | 3 | [![Greenkeeper badge](https://badges.greenkeeper.io/arvitaly/gopherjs-electron.svg)](https://greenkeeper.io/) 4 | Atom/Electron (https://github.com/atom/electron/) desktop apps with Go. Package use https://github.com/gopherjs/gopherjs. 5 | 6 | [![Build Status](https://travis-ci.org/arvitaly/gopherjs-electron.svg?branch=master)](https://travis-ci.org/arvitaly/gopherjs-electron) 7 | 8 | # Install 9 | 10 | Package require electron-prebuilt npm-module 11 | 12 | npm install -g electron-prebuilt 13 | 14 | go get github.com/arvitaly/gopherjs-electron 15 | 16 | # Usage 17 | 18 | Look source-code and tests)) //TODO 19 | 20 | # Test 21 | 22 | For tests used Jasmine (http://jasmine.github.io/) and adapter fo go https://github.com/arvitaly/gopherjs-jasmine 23 | 24 | go get github.com/arvitaly/gopherjs-jasmine 25 | npm install electron-prebuilt -g 26 | npm install 27 | npm test 28 | 29 | # Docker 30 | 31 | Also you can use docker-image for electron (includes NodeJS, Golang, gopherjs, electron-prebuilt). Example for run app in docker-container in file .drone.yml (config for drone.io CI) 32 | 33 | docker pull arvitaly/electron-go 34 | -------------------------------------------------------------------------------- /spec/webContents-spec.go: -------------------------------------------------------------------------------- 1 | package spec 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/arvitaly/gopherjs-electron" 7 | ) 8 | import jasmine "github.com/arvitaly/gopherjs-jasmine" 9 | import "github.com/gopherjs/gopherjs/js" 10 | 11 | var wsTests = func() bool { 12 | jasmine.Describe("WebContents", func() { 13 | jasmine.ItAsync("ExecuteJavascript", func(done func()) { 14 | var w = electron.NewBrowserWindow(nil) 15 | w.LoadURL("file:///"+js.Global.Get("process").Call("cwd").String()+"/spec/page1.html", nil) 16 | w.GetWebContents().OnWillNavigate(func(event *js.Object, url string) { 17 | jasmine.Expect(url).ToBe("file:///url2") 18 | w.Close() 19 | done() 20 | }) 21 | w.GetWebContents().ExecuteJavaScript("location.href='file:///url2'", nil) 22 | }) 23 | jasmine.It("UserAgent", func() { 24 | var w = electron.NewBrowserWindow(nil) 25 | w.GetWebContents().SetUserAgent("user1") 26 | jasmine.Expect(w.GetWebContents().GetUserAgent()).ToBe("user1") 27 | w.Close() 28 | }) 29 | jasmine.It("OnCrashed", func() { 30 | var w = electron.NewBrowserWindow(&map[string]interface{}{ 31 | "webSecurity": false, 32 | }) 33 | var crashed = make(chan bool) 34 | w.GetWebContents().OnCrashed(func() { 35 | go func() { 36 | crashed <- true 37 | }() 38 | }) 39 | w.LoadURL("chrome://crash/", nil) 40 | <-crashed 41 | w.Close() 42 | }) 43 | jasmine.AfterAllAsync(func(done func()) { 44 | time.AfterFunc(time.Millisecond*10, done) 45 | }) 46 | }) 47 | return true 48 | }() 49 | -------------------------------------------------------------------------------- /browserWindow.go: -------------------------------------------------------------------------------- 1 | package electron 2 | 3 | import "github.com/gopherjs/gopherjs/js" 4 | 5 | type BrowserWindow interface { 6 | GetWebContents() WebContents 7 | LoadURL(url string, opts *map[string]interface{}) 8 | GetTitle() string 9 | Close() 10 | Destroy() 11 | 12 | //Emitted when the window is going to be closed. It's emitted before the beforeunload and unload event of the DOM. Calling event.preventDefault() will cancel the close. 13 | OnClose(listener func(event *js.Object)) 14 | //Emitted when the window is closed. After you have received this event you should remove the reference to the window and avoid using it anymore. 15 | OnClosed(listener func()) 16 | } 17 | type _BrowserWindow struct { 18 | *js.Object 19 | WebContents WebContents 20 | } 21 | 22 | func (w *_BrowserWindow) GetWebContents() WebContents { 23 | return w.WebContents 24 | } 25 | func (w *_BrowserWindow) LoadURL(url string, opts *map[string]interface{}) { 26 | w.Call("loadURL", url, opts) 27 | } 28 | 29 | func (w *_BrowserWindow) GetTitle() string { 30 | return w.Call("getTitle").String() 31 | } 32 | func (w *_BrowserWindow) Destroy() { 33 | w.Call("destroy") 34 | } 35 | func (w *_BrowserWindow) Close() { 36 | w.Call("close") 37 | } 38 | func (w *_BrowserWindow) OnClose(listener func(event *js.Object)) { 39 | w.Call("on", "close", func(event *js.Object) { 40 | listener(event) 41 | }) 42 | } 43 | func (w *_BrowserWindow) OnClosed(listener func()) { 44 | w.Call("on", "closed", func() { 45 | listener() 46 | }) 47 | } 48 | func (w *_BrowserWindow) HookWindowMessage(message int, callback func(result ...interface{})) { 49 | 50 | } 51 | -------------------------------------------------------------------------------- /spec/browserWindow-spec.go: -------------------------------------------------------------------------------- 1 | package spec 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/arvitaly/gopherjs-electron" 7 | jasmine "github.com/arvitaly/gopherjs-jasmine" 8 | "github.com/gopherjs/gopherjs/js" 9 | ) 10 | 11 | var bwTest = func() bool { 12 | jasmine.Describe("BrowserWindow", func() { 13 | var w electron.BrowserWindow 14 | jasmine.It("New", func() { 15 | w = electron.NewBrowserWindow(&map[string]interface{}{ 16 | "title": "title1", 17 | }) 18 | jasmine.Expect(w.GetTitle() == "title1").ToBeTruthy() 19 | }) 20 | jasmine.ItAsync("LoadUrl", func(done func()) { 21 | 22 | w = electron.NewBrowserWindow(&map[string]interface{}{ 23 | "title": "title2", 24 | }) 25 | w.LoadURL("file:///"+js.Global.Get("process").Call("cwd").String()+"/spec/page1.html", nil) 26 | w.GetWebContents().OnDidStopLoading(func() { 27 | jasmine.Expect(w.GetTitle()).ToBe("Page1") 28 | done() 29 | }) 30 | 31 | }) 32 | jasmine.It("OnClose and OnClosed", func() { 33 | var w = electron.NewBrowserWindow(nil) 34 | var closec = make(chan bool) 35 | var first = true 36 | w.OnClose(func(event *js.Object) { 37 | if first { 38 | event.Call("preventDefault") 39 | } 40 | first = false 41 | go func() { 42 | closec <- true 43 | }() 44 | 45 | }) 46 | w.Close() 47 | <-closec 48 | var closed = make(chan bool) 49 | w.OnClosed(func() { 50 | go func() { 51 | closed <- true 52 | }() 53 | }) 54 | w.Close() 55 | 56 | <-closec 57 | <-closed 58 | }) 59 | jasmine.AfterAllAsync(func(done func()) { 60 | time.AfterFunc(time.Millisecond*100, func() { 61 | w.Close() 62 | done() 63 | }) 64 | }) 65 | }) 66 | return true 67 | }() 68 | -------------------------------------------------------------------------------- /webContents.go: -------------------------------------------------------------------------------- 1 | package electron 2 | 3 | import "github.com/gopherjs/gopherjs/js" 4 | 5 | type WebContents interface { 6 | SetUserAgent(userAgent string) 7 | ExecuteJavaScript(code string, UserGesture *bool) 8 | GetUserAgent() string 9 | /*Events*/ 10 | //Corresponds to the points in time when the spinner of the tab stopped spinning. 11 | OnDidStopLoading(listener func()) 12 | OnDidFailLoad(listener func(event *js.Object, errorCode int, errorDescription string, validatedURL string)) 13 | OnWillNavigate(listener func(event *js.Object, url string)) 14 | 15 | //Emitted when the renderer process has crashed. 16 | OnCrashed(listener func()) 17 | } 18 | 19 | type _WebContents struct { 20 | *js.Object 21 | EventEmitter 22 | } 23 | 24 | func (w *_WebContents) GetUserAgent() string { 25 | return w.Call("getUserAgent").String() 26 | } 27 | 28 | func (w *_WebContents) SetUserAgent(userAgent string) { 29 | w.Call("setUserAgent", userAgent) 30 | } 31 | 32 | // HttpReferrer string //A HTTP Referrer url. 33 | // UserAgent string //A user agent originating the request. 34 | // ExtraHeaders string //Extra headers separated by "\n" 35 | func (w *_WebContents) LoadURL(url string, opts *map[string]interface{}) { 36 | w.Call("loadURL", url, opts) 37 | } 38 | func (w *_WebContents) ExecuteJavaScript(code string, userGesture *bool) { 39 | w.Call("executeJavaScript", code, userGesture) 40 | } 41 | func (w *_WebContents) OnDidFailLoad(listener func(event *js.Object, errorCode int, errorDescription string, validatedURL string)) { 42 | w.Call("on", "did-fail-load", func(event *js.Object, errorCode *js.Object, errorDescription *js.Object, validatedURL *js.Object) { 43 | listener(event, errorCode.Int(), errorDescription.String(), validatedURL.String()) 44 | }) 45 | } 46 | func (w *_WebContents) OnWillNavigate(listener func(event *js.Object, url string)) { 47 | w.Call("on", "will-navigate", func(event *js.Object, url *js.Object) { 48 | listener(event, url.String()) 49 | }) 50 | } 51 | 52 | func (w *_WebContents) OnDidStopLoading(listener func()) { 53 | w.Call("on", "did-stop-loading", func() { 54 | go func() { 55 | listener() 56 | }() 57 | }) 58 | } 59 | 60 | //TODO add test? 61 | func (w *_WebContents) OnCrashed(listener func()) { 62 | w.Call("on", "crashed", listener) 63 | } 64 | -------------------------------------------------------------------------------- /electron.go: -------------------------------------------------------------------------------- 1 | package electron 2 | 3 | import "github.com/gopherjs/gopherjs/js" 4 | 5 | var jsElectron = js.Global.Get("require").Invoke("electron") 6 | 7 | func GetApp() App { 8 | return &_App{jsElectron.Get("app")} 9 | } 10 | 11 | //It creates a new BrowserWindow with native properties as set by the options 12 | //type BrowserWindowOptions struct { 13 | // Width int //Window's width in pixels. Default is 800 14 | // Height int //Window's height in pixels. Default is 600 15 | // X int //Window's left offset from screen. Default is to center the window. 16 | // Y int //Window's top offset from screen. Default is to center the window. 17 | // UseContentSize bool //The width and height would be used as web page's size, which means the actual window's size will include window frame's size and be slightly larger. Default is false. 18 | // Center bool //Show window in the center of the screen. 19 | // MinWidth int //Window's minimum width. Default is 0. 20 | // MinHeight int //Window's minimum height. Default is 0. 21 | // MaxWidth int //Window's maximum width. Default is no limit. 22 | // MaxHeight int //Window's maximum height. Default is no limit. 23 | // Resizable bool //Whether window is resizable. Default is true. 24 | // AlwaysOnTop bool //Whether the window should always stay on top of other windows. Default is false. 25 | // Fullscreen bool //Whether the window should show in fullscreen. When set to false the fullscreen button will be hidden or disabled on OS X. Default is false. 26 | // SkipTaskbar bool //Whether to show the window in taskbar. Default is false. 27 | // Kiosk bool //The kiosk mode. Default is false. 28 | // Title string //Default window title. Default is "Electron". 29 | // Icon interface{} //The window icon, when omitted on Windows the executable's icon would be used as window icon. 30 | // Show bool //Whether window should be shown when created. Default is true. 31 | // Frame bool //Specify false to create a Frameless Window. Default is true 32 | // AcceptFirstMouse bool 33 | // DisableAutoHideCursor bool 34 | // AutoHideMenuBar bool 35 | // EnableLargerThanScreen bool 36 | // BackgroundColor string `js:"backgroundColor"` 37 | // DarkTheme bool 38 | // Transparent bool 39 | // Type string 40 | // TitleBarStyle string 41 | // WebPreferences interface{} 42 | // NodeIntegration bool 43 | //} 44 | func NewBrowserWindow(opts *map[string]interface{}) BrowserWindow { 45 | var bw = jsElectron.Get("BrowserWindow").New(opts) 46 | return &_BrowserWindow{Object: bw, WebContents: &_WebContents{Object: bw.Get("webContents")}} 47 | } 48 | --------------------------------------------------------------------------------