Building Desktop App in Go using Wails

📅 Mon, May 6, 2019 ⏱️ 5-minute read

desktop+wails.jpg

This post is a text version of packagemain #6: Building Desktop App in Go using Wails video.

As we all know, Go is mostly used to build APIs, web backends, CLI tools. But what’s interesting is that Go can be used in places we were not expecting to see it.

For example, we can build a Desktop App with Go and Vue.js using Wails framework.

This framework is new and still in beta, but I was surprised how easy it was to develop, build and package an app with it.

Wails provides the ability to wrap both Go code and a web frontend into a single binary. The Wails CLI makes this easy for you, by handling project creation, compilation, and bundling.

App

We will build a very simple app to display CPU Usage of my machine in real time. And if you have time and like Wails, you can come up with something more creative and complex.

Installation

Wails CLI can be installed with go get. After installation, you should set it up using wails setup command.

go get github.com/wailsapp/wails/cmd/wails
wails setup

Then let’s bootstrap our project with the name cpustats:

wails init
cd cpustats

Our project consists of Go backend and Vue.js frontend. main.go will be our entrypoint, in which we can include any other dependencies, there is also go.mod file to manage them. frontend folder contains Vue.js components, webpack and CSS.

Concepts

There are 2 main components to share data between Backend and Frontend: Binding and Events.

Binding is a single method that allows you to expose (bind) your Go code to the frontend.

Also, Wails provides a unified Events system similar to Javascript’s native events system. This means that any event that is sent from either Go or Javascript can be picked up by either side. Data may be passed along with any event. This allows you to do neat things like have background processes running in Go and notifying the frontend of any updates.

Backend

Let’s develop a backend part first, to get CPU Usage and send it to the frontend using bind method.

We will create a new package and define a type which I’ll expose (bind) to the frontend.

pkg/sys/sys.go:

package sys

import (
	"math"
	"time"

	"github.com/shirou/gopsutil/cpu"
	"github.com/wailsapp/wails"
)

// Stats .
type Stats struct {
	log *wails.CustomLogger
}

// CPUUsage .
type CPUUsage struct {
	Average int `json:"avg"`
}

// WailsInit .
func (s *Stats) WailsInit(runtime *wails.Runtime) error {
	s.log = runtime.Log.New("Stats")
	return nil
}

// GetCPUUsage .
func (s *Stats) GetCPUUsage() *CPUUsage {
	percent, err := cpu.Percent(1*time.Second, false)
	if err != nil {
		s.log.Errorf("unable to get cpu stats: %s", err.Error())
		return nil
	}

	return &CPUUsage{
		Average: int(math.Round(percent[0])),
	}
}

If your struct has a WailsInit method, Wails will call it at startup. This allows you to do some initialisation before the main application is launched.

Import sys package in main.go and bind Stats instance to frontend:

package main

import (
	"github.com/leaanthony/mewn"
	"github.com/plutov/packagemain/cpustats/pkg/sys"
	"github.com/wailsapp/wails"
)

func main() {
	js := mewn.String("./frontend/dist/app.js")
	css := mewn.String("./frontend/dist/app.css")

	stats := &sys.Stats{}

	app := wails.CreateApp(&wails.AppConfig{
		Width:  512,
		Height: 512,
		Title:  "CPU Usage",
		JS:     js,
		CSS:    css,
		Colour: "#131313",
	})
	app.Bind(stats)
	app.Run()
}

Frontend

We bind the stats instance from Go, which can be used in frontend by callind window.backend.Stats. If we want to call a function GetCPUUsage() it will return us a Promise.

window.backend.Stats.GetCPUUsage().then(cpu_usage => {
    console.log(cpu_usage);
})

To build the whole project into single binary we should run wails build, -d flag can be added to build a debuggable version. It will create a binary with a name matching the project name.

Let’s test if it works by simply displaying the CPU Usage value on the screen:

wails build -d
./cpustats

Events

We sent CPU Usage value to frontend using Binding, now let’s try different approach, let’s create a timer on Backend which will send CPU Usage values in the background using Events approach. Then we can subscribe to the event in Javascript.

In Go we can do it in WailsInit function:

func (s *Stats) WailsInit(runtime *wails.Runtime) error {
	s.log = runtime.Log.New("Stats")

	go func() {
		for {
			runtime.Events.Emit("cpu_usage", s.GetCPUUsage())
			time.Sleep(1 * time.Second)
		}
	}()

	return nil
}

In Vue.js we can subscribe to this event when component is mounted (or any other place):

mounted: function() {
    wails.events.on("cpu_usage", cpu_usage => {
        if (cpu_usage) {
            console.log(cpu_usage.avg);
        }
    });
}

Gauge Bar

It would be nice to display CPU Usage with a gauge bar, so we will include a third party dependency for that, simply by using npm:

npm install --save apexcharts
npm install --save vue-apexcharts

Then import it to main.js file:

import VueApexCharts from 'vue-apexcharts'

Vue.use(VueApexCharts)
Vue.component('apexchart', VueApexCharts)

Now we can display our CPU Usage using apexcharts, and update the values of the component by receiving an event from Backend:

<template>
  <apexchart type="radialBar" :options="options" :series="series"></apexchart>
</template>

<script>
export default {
  data() {
    return {
      series: [0],
      options: {
        labels: ['CPU Usage']
      }
    };
  },
  mounted: function() {
    wails.events.on("cpu_usage", cpu_usage => {
      if (cpu_usage) {
        this.series = [ cpu_usage.avg ];
      }
    });
  }
};
</script>

To change styles we can directly modify the src/assets/css/main.css or define them in components.

Final Build and Run

wails build -d
./cpustats

desktop-app-wails

Conclusion

I really enjoyed working with Wails, and the Events concept makes it really easy to control your application’s state.

Check it out at wails.app or on Github at github.com/wailsapp/wails

Full code for this article on GitHub