GoDoc

Introduction

Qt is a cross-platform application framework that is used for developing application software that can be run on various software and hardware platforms with little or no change in the underlying codebase, while still being a native application with native capabilities and speed.

Go (often referred to as golang) is a free and open source programming language created at Google.

This binding is a thin layer on top of Qt’s C++ API (with a one-to-one mapping) and allows you to write Qt applications entirely in Go, it comes along with some tools to make working with it more convenient.

Beside the main effort to ease the development of application software by making Qt’s API accessible from Go, the second biggest effort went into simplifying the development and deployment processes.

Screenshots

Status

WIP

Most of Qt’s API is accessible from Go.

It should already contain everything you need to build fully featured applications.

If you still miss something, please open an issue.

Also there have been no release so far, so please pin the repo to a specific commit that is known to work for you.

Targets

The following targets are currently supported:

Target Arch Linkage Docker Deployment Host OS
Windows (32 / 64) (dynamic / static / system libs) Yes Any
Android (+Wear) arm dynamic Yes Any
Android-Emulator (+Wear) 32 dynamic No Windows/macOS/Linux
Linux 64 (dynamic / system libs) Yes Any
Raspberry Pi (1/2/3) arm (dynamic / system libs) Yes Any
macOS 64 (dynamic / system libs) No macOS
iOS (arm + arm64) static No macOS
iOS-Simulator (32 + 64) static No macOS
SailfishOS arm system libs No Windows/macOS/Linux
SailfishOS-Emulator 32 system libs No Windows/macOS/Linux
AsteroidOS arm system libs No Linux

FAQ

Docker

The easiest way to get started is to use the pre-built docker images.

Tag Compressed Size
linux 1 GB
android 3 GB
windows_32_shared 1 GB
windows_32_static 2 GB
windows_64_shared 1 GB
windows_64_static 2 GB
rpi1 2 GB
rpi2 2 GB
rpi3 2 GB

(If you want to switch between the 32/64-bit and the dynamic/static linkage for the windows target, you can use the QT_MXE_ARCH and QT_MXE_STATIC environmental variables like you would do if install MXE directly on your machine)

You can download an image by running: docker pull therecipe/qt:TAG

These images contain Go, Qt and all dependencies that may be necessary to deploy to your target.

You can look into the Dockerfiles that are used to create these images here.

The images can be used for the following:

Installation

If you want to build applications for macOS or iOS, then you need to take the regular setup on macOS.

Minimal Setup

Regular Setup

Please make the environmental variables persistent if you override them.

You can do this by either editing you $HOME/.bash_profile or $HOME/.profile on macOS or Linux.

On Windows you can simply use the advanced system settings.

These are some general environmental variables, you can find the target specific environmental variables here.

Variable Default Type Note
QT_VERSION 5.8.0 string can also be set by using the -qt_version flag
QT_DIR $HOME/Qt5.8.0 or C:\Qt\Qt5.8.0 string can also be set by using the -qt_dir flag
QT_STUB false bool is set to true during the minimal setup
QT_DEBUG false bool set to true if you want to print function names at runtime
QT_QMAKE_DIR   string can be used to make use of custom versions of Qt

Desktop

Windows
Official (with Android support)
MSYS2
macOS
Official (with iOS/Android support)
HomeBrew (no deployments possible)

Linux

Official (with Android support)
Pkg-Config (no deployments possible)
Cross compile to Windows on Debian/Ubuntu

Mobile

Android / Android-Emulator
iOS
SailfishOS
AsteroidOS

Embedded

Raspberry Pi
Raspbian + pre-built Qt 5.7
Arch Linux + self-compiled Qt 5.8
Additional Modules
WebKit

Target Specific Infos And Settings

Desktop

Windows

General

You may need to export CGO_ENABLED=1 and GOARCH=386 if you want to use go build on windows.

Depending on your editor or IDE you may also need to export GOARCH=386 to make the code completion work.

It’s recommended to use qtdeploy -fast build path/to/your/project instead of the direct usage of go build.

Environmental Variables

Windows Host

Variable Default Type Note
QT_MSYS2 false bool set to true if you want to use the msys2 version of Qt
QT_MSYS2_DIR C:\msys32 or C:\msys64 string qtsetup check will display this with the prefix detected by QT_MSYS2_ARCH
QT_MSYS2_ARCH 386 string set to amd64 if you want to deploy 64 bit applications
QT_MSYS2_STATIC false bool set to true if you want to deploy statically linked applications

Linux or macOS Host

Variable Default Type Note
QT_MXE_DIR /usr/lib/mxe string can be set to use mxe from an alternative location
QT_MXE_ARCH 386 string set to amd64 if you want to deploy 64 bit applications
QT_MXE_STATIC false bool set to true if you want to deploy statically linked applications

Branding

You can use the windows folder at the root of your project to let qtdeploy automatically bundle your assets.

If you want to use a custom icon for your binary, you may need to use windres.

To do so, create a file called icon.rc with the following content in your root project folder.

Rename your icon to icon.ico and place it alongside the icon.rc

Run windres like this: path/to/windres path/to/project/icon.rc -o path/to/project/icon_windows.syso

windres can be usually found inside the same folder as rcc on a windows system (for example: Qt5.8.0/5.8/mingw53_32/bin/windres.exe)

Use qtdeploy or go build as usuall, the *.syso file should be automatically detected and be added to your binary.

You can find more infos here: https://doc.qt.io/qt-5/appicon.html

macOS

Environmental Variables

Variable Default Type Note
QT_HOMEBREW false bool set to true if you want to use the homebrew version of Qt
XCODE_DIR /Applications/Xcode.app string can be set to use Xcode from an alternative location

Branding

You can use the darwin folder at the root of your project to let qtdeploy automatically bundle your assets.

project_name
├── darwin
│   └── Contents
│       ├── Info.plist
│       └── Resources
│           └── project_name.icns
├── project.go
└── qml
    └── project.qml

Furthermore you can use the Info.plist found inside deploy/darwin/project_name.app as a template to build upon.

Linux

Environmental Variables

Variable Default Type Note
QT_DISTRO arch/fedora/suse/ubuntu/gentoo string set this to another distro if you want to change the values for DOC and MISC dir
QT_PKG_CONFIG false bool set this to true if you want to use the Qt version of your systems package manger
QT_DOC_DIR “depends on the distro” string set this to the documentation (*.index) folder location
QT_MISC_DIR “depends on the distro” string  

Branding

You can use the linux folder at the root of your project to let qtdeploy automatically bundle your assets.

Mobile

Android / Android-Emulator

Environmental Variables

Variable Default Type Note
JDK_DIR JAVA_HOME string can be also set by setting JAVA_HOME
ANDROID_SDK_DIR $HOME/android-sdk-linux or $HOME/android-sdk-macosx or C:\android-sdk-windows string can be also set by setting ANDROID_SDK_ROOT
ANDROID_NDK_DIR $HOME/android-ndk-r14b or C:\android-ndk-r14b string can be also set by setting ANDROID_NDK_ROOT

Branding

You can use the android or android-emulator folder at the root of your project to let qtdeploy automatically bundle your assets.

project_name
├── android
│   ├── AndroidManifest.xml
│   ├── alias.txt
│   ├── build.gradle
│   ├── gradle
│   │   └── wrapper
│   │       └── gradle-wrapper.properties
│   ├── icon.png
│   ├── password.txt
│   ├── project_name.keystore
│   └── res
│       ├── drawable-hdpi
│       │   └── icon.png
│       ├── drawable-ldpi
│       │   └── icon.png
│       └── drawable-mdpi
│           └── icon.png
├── deploy
│   └── android
│       └── build
├── project.go
└── qml
    └── project.qml

The content of the android or android-emulator folder will be copied prior gradle is used to create an *.apk.

Furthermore you can use the AndroidManifest.xml found inside deploy/android/build as a template to build upon.

This can be useful if you want to change the package identifier, the icon or add more permissions.

You could for example add an android:icon="@drawable/icon" attribute to application in AndroidManifest.xml to set replace the default icon with your own.

If you want to have your *.apk signed automatically, then you need to create an alias.txt, password.txt and an project_name.keystore inside the android folder.

Or you could sign it manually as well.

Java Native Interface (JNI)

You can call Java functions directly from Go with the help of the androidextras package.

Callbacks into Go code from Java are also possible (with some manual labor).

Take a look at the examples to see how it works.

Android Wear

To get your app working on Android Wear, you just need to add android:theme="@android:style/Theme.DeviceDefault" to the application node of your custom AndroidManifest.xml before running qtdeploy.

You can find a working example here.

iOS

Branding

You can use the ios or ios-simulator folder at the root of your project to let qtdeploy automatically bundle your assets.

Furthermore you can sign your app with the help of Xcode.

SailfishOS

Environmental Variables

Variable Default Type Note
VIRTUALBOX_DIR “depends on the host os” string set this to override the default path to the vboxmanage binary
SAILFISH_DIR $HOME/SailfishOS or C:\SailfishOS string set this to override the default path to the root dir of your sailfish installation

Branding

You can use the sailfish or sailfish-emulator folder at the root of your project to let qtdeploy automatically bundle your assets.

project_name
├── project.go
├── qml
│   └── project.qml
└── sailfish
    ├── harbour-project_name.png
    ├── project_name.desktop
    └── project_name.spec

Furthermore you can use the *.desktop and *.spec files found inside deploy/sailfish as a template to build upon.

AsteroidOS

Embedded

Raspberry Pi

Environmental Variables

Variable Default Type Note
RPI_TOOLS_DIR $HOME/raspi/tools string set this to override the default path to the raspi tools repo
RPI_COMPILER arm-rpi-4.9.3-linux-gnueabihf string gcc-linaro-arm-linux-gnueabihf-raspbian-x64 is used for the docker images

Branding

You can use the rpiX folders at the root of your project to let qtdeploy automatically bundle your assets.

Tools

The tools can be found either in $GOPATH/bin or $GOBIN and are symlinked into your PATH during the setup.

If you ran the minimal setup and/or depend on the docker images for the specific target, then you need to use the -docker flag when you use them.

qtsetup

The use of qtsetup is rarely necessary after you are done with the inital setup.

But you can use it to dump some environmental infos by running: qtsetup check [TARGET]

And it can be used to update the tools by running: qtsetup update

Or be used to upgrade the whole repo by running: qtsetup upgrade (you will need to re-run the setup after this)

Run qtsetup -help for more infos.

qtdeploy

qtdeploy is used to deploy your applications.

You can compile and deploy to your desktop target by running: qtdeploy build desktop [path/to/your/project]

(it’s possible to obtain the path, if you are currently inside the project folder)

The other valid targets are:

Target
windows
darwin
linux
android
android-emulator
ios
ios-simulator
sailfish
sailfish-emulator
rpi1
rpi2
rpi3
asteroid

So for example running:

qtdeploy build android [path/to/your/project]

will deploy your project for the android target.

and running:

qtdeploy -docker build android [path/to/your/project]

will use the docker image for it.

You can speedup the compilation by using the -fast flag. (only for the desktop target)

qtdeploy will also call qtrcc, qtmoc and qtminimal for you.

There will be various files created during the compilation, it’s recommended to not manually edit or remove them.

Run qtdeploy -help for more infos.

qtrcc

qtrcc is a wrapper around Qt’s rcc and can be used to bundle resources with your code.

You can use it to bundle resources for the desktop target like this: qtrcc desktop path/to/your/project

(it’s possible to obtain the path, if you are currently inside the project folder)

qtrcc will then automatically bundle all resources from the qml sub-folder (path/to/your/project/qml).

If you want to re-use your current .qrc file(s) instead, or simply want to have more control about your resouces, then you can place your .qrc file(s) inside the root folder.

Or if you need even more control, then you can just use rcc directly as well, but make sure to obtain one of these rcc_cgo_*.go files to place them alongside with your rcc generated .cpp file.

Invoking qtrcc will generate at least 3 different files, it’s recommended to not manually edit or remove them.

Futhermore qtquickcontrols2.conf files are automatically detected inside the root folder and bundled as well.

You can edit the *.qrc files by hand, or with the help of the Qt Creator.

Run qtrcc -help for more infos.

qtmoc

qtmoc is a wrapper around Qt’s moc and can be used to create sub-classes of Qt classes and to extend them with your own constructors, signals, slots and properties.

It’s recommended that you get yourself familiar with Qt’s signal and slot mechanism first: https://doc.qt.io/qt-5/signalsandslots.html

You can use qtmoc like this: qtmoc desktop path/to/your/project or qtmoc desktop path/to/your/sub-package

(it’s possible to obtain the path, if you are currently inside the project or sub-package folder)

For this to work you need to create a moc struct first:

package main

import "github.com/therecipe/qt/core"

//this struct will let qtmoc generate the necessary Go and C++ code.
//such as the NewExampleStruct and DestroyExampleStruct functions
type exampleStruct struct {

	//let qtmoc know what class you want to sub-class
	//this can be any class that is derived from QObject
	//and could also be one of your own moc classes
	//it's important that you anonymously embed the type as a value here
	//qtmoc will ignore this struct otherwise
	core.QObject

	//this will let qtmoc know that you want to have the "init" function called
	//when you call NewExampleStruct
	_ func() `constructor:"init"`

	//this will let qtmoc know that you want a signal called "firstSignal"
	//so that the related Connect* Disconnect* functions can be created
	_ func() `signal:"firstSignal"`

	//the signal can also have parameters, but no return parameter
	//return parameters are only possible if you create a slot
	_ func(bool, int, string, []string, map[string]string) `signal:"secondSignal"`

	//you can also let the signal accept a single *core.QObject an array or a map
	_ func(*core.QObject, []*core.QObject, map[string]*core.QObject) `signal:"thirdSignal"`

	//as a special addition to the supported Go primitives, you can also use Go errors in signals/slots/properties
	_ func(error) `signals:"fourthSignal"`

	//this will let qtmoc know that you want a slot called "firstSlot"
	_ func() `slot:"firstSlot"`

	//a slot can be created in the same way as a signal, but it can additionally return a single argument
	_ func(string) string               `slot:"secondSlot"`
	_ func(*core.QObject) *core.QObject `slot:"thirdSlot"`
	_ func() error                      `slot:"fourthSlot"`

	//this will let qtmoc know that you want a property called "firstProperty"
	//there will be helper getter + setter functions and a changed signal created called:
	//FirstProperty (IsFirstProperty for bools), SetFirstProperty, FirstProperyChanged
	_ string `property:"firstProperty"`
}

//this function will be automatically called, when you use the `NewExampleStruct` function
func (s *exampleStruct) init() {
	//here you can do some initializing
	s.SetFirstProperty("defaultString")
	s.ConnectFirstSignal(func() { println("do something here") })
	s.ConnectSecondSignal(s.secondSignal)
}

func (s *exampleStruct) secondSignal(v0 bool, v1 int, v2 string, v3 []string, v4 map[string]string) {
	println("do something here")
}

There are some additions planned, such as:

Run qtmoc -help for more infos.

qtminimal

qtminimal is not needed to be ran manually, as it is just used to reduce the overall compiled binary size, by analysing your code and re-generating a sub-set of the binding.

You can use it to generate the sub-set for the desktop target like this: qtminimal desktop path/to/your/project

(it’s possible to obtain the path, if you are currently inside the project folder)

Then you could use go build -tags=minimal ... to compile against the slimmed down version of the binding.

Note that qtminimal should be always ran after the optional use of qtrcc or qtmoc.

Run qtminimal -help for more infos.

Speed up your development cycle

It’s recommended to use qtdeploy -fast build ... if you want to have your project compiled as fast as possible.

This will skip most of the actions qtdeploy would take otherwise, such as running qtmoc, qtminimal and deploying your project.

Note that this option is only available for the desktop target, and it’s mandatory to run the normal deployment at least once before using the -fast flag.

Beside the option to use the -fast flag, you could also just use qtrcc, qtmoc and qtminimal manually with go build.

Hello World

Basic

widgets

$GOPATH/src/qtexample/main.go

package main

import (
	"os"

	"github.com/therecipe/qt/widgets"
)

func main() {

	widgets.NewQApplication(len(os.Args), os.Args)

	//create a window
	window := widgets.NewQMainWindow(nil, 0)
	window.SetWindowTitle("Hello World Example")
	window.SetMinimumSize2(200, 200)

	//create a layout
	layout := widgets.NewQVBoxLayout()

	//create a widget and set the layout
	widget := widgets.NewQWidget(nil, 0)
	widget.SetLayout(layout)

	//create a lineedit and add it to the layout
	input := widgets.NewQLineEdit(nil)
	input.SetPlaceholderText("1. write something")
	layout.AddWidget(input, 0, 0)

	//create a button and add it to the layout
	button := widgets.NewQPushButton2("2. click me", nil)
	button.ConnectClicked(func(checked bool) {
		widgets.QMessageBox_Information(nil, "OK", input.Text(), widgets.QMessageBox__Ok, widgets.QMessageBox__Ok)
	})
	layout.AddWidget(button, 0, 0)

	//add the widget as the central widget to the window
	window.SetCentralWidget(widget)

	//show the window
	window.Show()

	//enter the main event loop
	widgets.QApplication_Exec()
}
qml/quick

$GOPATH/src/qtexample/main.go

package main

import (
	"os"

	"github.com/therecipe/qt/core"
	"github.com/therecipe/qt/gui"
	"github.com/therecipe/qt/qml"
	"github.com/therecipe/qt/quickcontrols2"
)

func main() {

	//enable high dpi scaling
	core.QCoreApplication_SetAttribute(core.Qt__AA_EnableHighDpiScaling, true)

	gui.NewQGuiApplication(len(os.Args), os.Args)

	//use the material style for qml controls
	//"universal" is also available
	quickcontrols2.QQuickStyle_SetStyle("material")

	//create a qml application
	view := qml.NewQQmlApplicationEngine(nil)

	//load the main qml file
	view.Load(core.NewQUrl3("qrc:///qml/main.qml", 0))

	//enter the main event loop
	gui.QGuiApplication_Exec()
}

$GOPATH/src/qtexample/qml/main.qml

import QtQuick 2.7
import QtQuick.Controls 2.1

ApplicationWindow {
  id: window

  visible: true
  title: "Hello World Example"
  minimumWidth: 400
  minimumHeight: 400

  Column {
    anchors.centerIn: parent

    TextField {
      id: input

      anchors.horizontalCenter: parent.horizontalCenter

      placeholderText: "1. write something"
    }

    Button {
      anchors.horizontalCenter: parent.horizontalCenter

      text: "2. click me"
      onClicked: dialog.open()
    }
  }

  Dialog {
    contentWidth: window.width / 2
    contentHeight: window.height / 4

    id: dialog
    standardButtons: Dialog.Ok

    contentItem: Label {
      text: input.text
    }
  }
}

Advanced

widgets

$GOPATH/src/qtexample/main.go

package main

import (
	"fmt"
	"os"
	"time"

	"github.com/therecipe/qt/core"
	"github.com/therecipe/qt/widgets"
)

//go:generate qtmoc
type MocLabel struct {
	widgets.QLabel

	_ func(int) `signal:"updateLabel"`
}

func main() {

	widgets.NewQApplication(len(os.Args), os.Args)

	//create a window
	window := widgets.NewQMainWindow(nil, 0)
	window.SetWindowTitle("Hello World Example")
	window.SetMinimumSize2(200, 200)

	//create a layout
	layout := widgets.NewQVBoxLayout()

	//create a widget and set the layout
	widget := widgets.NewQWidget(nil, 0)
	widget.SetLayout(layout)

	//create a moc label
	label := NewMocLabel(nil, 0)
	label.SetAlignment(core.Qt__AlignCenter)

	//wrap the setText function with a custom signal
	label.ConnectUpdateLabel(func(s int) {

		//we are back in the main thread
		//so it's safe to update the label now
		label.SetText(fmt.Sprintf("%v second(s)", s))
	})

	//setup a ticker to update the label in the background
	t := time.Now()
	ticker := time.NewTicker(1 * time.Second)
	go func() {
		for _ = range ticker.C {
			label.UpdateLabel(int(time.Since(t).Seconds()))
		}
	}()

	//add the label to the layout
	layout.AddWidget(label, 0, 0)

	//create a button and add it to the layout
	button := widgets.NewQPushButton2("reset ticker", nil)
	button.ConnectClicked(func(checked bool) {
		label.UpdateLabel(0)
		t = time.Now()
	})
	layout.AddWidget(button, 0, 0)

	//add the widget as the central widget to the window
	window.SetCentralWidget(widget)

	//show the window
	window.Show()

	//enter the main event loop
	widgets.QApplication_Exec()
}
qml/quick

$GOPATH/src/qtexample/main.go

package main

import (
	"os"
	"time"

	"github.com/therecipe/qt/core"
	"github.com/therecipe/qt/gui"
	"github.com/therecipe/qt/qml"
	"github.com/therecipe/qt/quickcontrols2"
)

//go:generate qtmoc
type MocBridge struct {
	core.QObject

	_ func(secs int) `signal:"updateLabel"`
	_ func()         `signal:"resetTicker"`
}

func main() {

	//enable high dpi scaling
	core.QCoreApplication_SetAttribute(core.Qt__AA_EnableHighDpiScaling, true)

	gui.NewQGuiApplication(len(os.Args), os.Args)

	//use the material style for qml controls
	//"universal" is also available
	quickcontrols2.QQuickStyle_SetStyle("material")

	//create a qml application
	app := qml.NewQQmlApplicationEngine(nil)

	//create a moc bridge
	bridge := NewMocBridge(nil)

	//setup a ticker to update the label in the background
	t := time.Now()
	ticker := time.NewTicker(1 * time.Second)
	go func() {
		for _ = range ticker.C {
			//call the connected "onUpdateLabel" signal on the qml side
			bridge.UpdateLabel(int(time.Since(t).Seconds()))
		}
	}()

	//connect the resetTicker function that is later called by qml
	bridge.ConnectResetTicker(func() {
		t = time.Now()
	})

	//make the bridge object accessible inside qml as "go"
	app.RootContext().SetContextProperty("go", bridge)

	//load the main qml file
	app.Load(core.NewQUrl3("qrc:///qml/main.qml", 0))

	//enter the main event loop
	gui.QGuiApplication_Exec()
}

$GOPATH/src/qtexample/qml/main.qml

import QtQuick 2.7
import QtQuick.Controls 2.1

ApplicationWindow {
  id: window

  visible: true
  title: "Hello World Example"
  minimumWidth: 400
  minimumHeight: 400

  Column {
    anchors.centerIn: parent

    Label {
      id: label

      anchors.horizontalCenter: parent.horizontalCenter

      Connections {
        target: go
        onUpdateLabel: {
          label.text = secs + " second(s)"
        }
      }
    }

    Button {
      anchors.horizontalCenter: parent.horizontalCenter

      text: "reset ticker"
      onClicked: {
        go.updateLabel(0)
        go.resetTicker()
      }
    }
  }
}

Continuous integration and deployment

There are a few possible ways how you can integrate this dependency into your CI or CD plan.

Misc

Qt documentation

#qt-binding Slack channel (invite)

An Video Overview of GUI technologies in Qt

Debugging and Profiling Qml Applications
Drag & Drop WYSIWYG Editors

Here is an example how to load the created *.ui files.

The created *.qml files can later be used like hand written qml files.

Code Editor Settings (for code completion)

On windows, depending on your editor or IDE you may also need to export GOARCH=386 to make the code completion work.

3rd party examples / demos / applications

License

This binding is available under LGPLv3.

Qt is available under multiple licenses: https://www.qt.io/licensing