rodrigocfd / windigo

Windows API and GUI in idiomatic Go.

Home Page:https://pkg.go.dev/github.com/rodrigocfd/windigo

Repository from Github https://github.comrodrigocfd/windigoRepository from Github https://github.comrodrigocfd/windigo

Go Reference GitHub go.mod Go version MIT License Lines of code

Windigo

Win32 API and GUI in idiomatic Go.

Windigo is designed to be familiar to C/C++ Win32 programmers, using the same concepts, and an API as close as possible to the original Win32 API. This allows most C/C++ Win32 tutorials and examples to be translated to Go.

Notably, Windigo is written 100% in pure Go – CGo is not used, just native syscalls.

Install

go get -u github.com/rodrigocfd/windigo

Examples

In the examples below, error checking is ommited for brevity.

GUI window

GUI window

The example below creates a window programmatically, and handles the button click. Also, it uses the minimal.syso provided in the _resources folder.

Screen capture

package main

import (
	"fmt"
	"runtime"

	"github.com/rodrigocfd/windigo/co"
	"github.com/rodrigocfd/windigo/ui"
)

func main() {
	runtime.LockOSThread() // important: Windows GUI is single-threaded

	ShowMainWindow()
}

// This struct represents our main window.
type MyWindow struct {
	wnd     *ui.Main
	lblName *ui.Static
	txtName *ui.Edit
	btnShow *ui.Button
}

// Displays the main window, blocking until it is closed.
func ShowMainWindow() int {
	wnd := ui.NewMain( // create the main window
		ui.OptsMain().
			Title("Hello you").
			Size(ui.Dpi(340, 80)).
			ClassIconId(101), // ID of icon resource, see _resources folder
	)

	lblName := ui.NewStatic( // create the child controls
		wnd,
		ui.OptsStatic().
			Text("Your name").
			Position(ui.Dpi(10, 22)),
	)
	txtName := ui.NewEdit(
		wnd,
		ui.OptsEdit().
			Position(ui.Dpi(80, 20)).
			Width(ui.DpiX(150)),
	)
	btnShow := ui.NewButton(
		wnd,
		ui.OptsButton().
			Text("&Show").
			Position(ui.Dpi(240, 19)),
	)

	me := &MyWindow{wnd, lblName, txtName, btnShow}
	me.events()
	return wnd.RunAsMain()
}

func (me *MyWindow) events() {
	me.btnShow.On().BnClicked(func() {
		msg := fmt.Sprintf("Hello, %s!", me.txtName.Text())
		me.wnd.Hwnd().MessageBox(msg, "Saying hello", co.MB_ICONINFORMATION)
	})
}

To compile the final .exe file, run the command:

go build -ldflags "-s -w -H=windowsgui"
Registry access

Registry access

package main

import (
	"github.com/rodrigocfd/windigo/co"
	"github.com/rodrigocfd/windigo/win"
)

func main() {
	// Open a registry key

	hKey, _ := win.HKEY_CURRENT_USER.RegOpenKeyEx(
		"Control Panel\\Mouse",
		co.REG_OPTION_NONE,
		co.KEY_READ) // open key as read-only
	defer hKey.RegCloseKey()

	// Read a single value from this key

	regVal, _ := hKey.RegQueryValueEx("Beep") // data can be string, uint32, etc.

	if strVal, ok := regVal.Sz(); ok { // try to extract a string value
		println("Beep is", strVal)
	}

	// Enumerate all values under this key

	namesVals, _ := hKey.RegEnumValue()
	for _, nameVal := range namesVals {
		if str, ok := nameVal.Val.Sz(); ok { // does it contain a string?
			println("Value str", nameVal.Name, str)
		} else if num, ok := nameVal.Val.Dword(); ok { // does it contain an uint32?
			println("Value int", nameVal.Name, num)
		} else {
			println("Value other", nameVal.Name)
		}
	}
}
Enumerating running processes

Enumerating running processes

The example below takes a process snapshot to list the running processes:

package main

import (
	"github.com/rodrigocfd/windigo/co"
	"github.com/rodrigocfd/windigo/win"
)

func main() {
	hSnap, _ := win.CreateToolhelp32Snapshot(co.TH32CS_SNAPPROCESS, 0)
	defer hSnap.CloseHandle()

	processes, _ := hSnap.EnumProcesses()
	for _, nfo := range processes {
		println("PID:", nfo.Th32ProcessID, "name:", nfo.SzExeFile())
	}

	println(len(processes), "found")
}
Taking a screenshot

Taking a screenshot

This complex example takes a screenshot using GDI and saves it to a BMP file.

package main

import (
	"unsafe"

	"github.com/rodrigocfd/windigo/co"
	"github.com/rodrigocfd/windigo/win"
)

func main() {
	cxScreen := win.GetSystemMetrics(co.SM_CXSCREEN)
	cyScreen := win.GetSystemMetrics(co.SM_CYSCREEN)

	hdcScreen, _ := win.HWND(0).GetDC()
	defer win.HWND(0).ReleaseDC(hdcScreen)

	hBmp, _ := hdcScreen.CreateCompatibleBitmap(int(cxScreen), int(cyScreen))
	defer hBmp.DeleteObject()

	hdcMem, _ := hdcScreen.CreateCompatibleDC()
	defer hdcMem.DeleteDC()

	hBmpOld, _ := hdcMem.SelectObjectBmp(hBmp)
	defer hdcMem.SelectObjectBmp(hBmpOld)

	_ = hdcMem.BitBlt(
		win.POINT{X: 0, Y: 0},
		win.SIZE{Cx: cxScreen, Cy: cyScreen},
		hdcScreen,
		win.POINT{X: 0, Y: 0},
		co.ROP_SRCCOPY,
	)

	bi := win.BITMAPINFO{
		BmiHeader: win.BITMAPINFOHEADER{
			Width:       cxScreen,
			Height:      cyScreen,
			Planes:      1,
			BitCount:    32,
			Compression: co.BI_RGB,
		},
	}
	bi.BmiHeader.SetSize()

	bmpObj, _ := hBmp.GetObject()
	bmpSize := bmpObj.CalcBitmapSize(bi.BmiHeader.BitCount)

	rawMem, _ := win.GlobalAlloc(co.GMEM_FIXED|co.GMEM_ZEROINIT, bmpSize)
	defer rawMem.GlobalFree()

	bmpSlice, _ := rawMem.GlobalLockSlice()
	defer rawMem.GlobalUnlock()

	_, _ = hdcScreen.GetDIBits(
		hBmp,
		0,
		int(cyScreen),
		bmpSlice,
		&bi,
		co.DIB_COLORS_RGB,
	)

	var bfh win.BITMAPFILEHEADER
	bfh.SetBfType()
	bfh.SetBfOffBits(uint32(unsafe.Sizeof(bfh) + unsafe.Sizeof(bi.BmiHeader)))
	bfh.SetBfSize(bfh.BfOffBits() + uint32(bmpSize))

	fout, _ := win.FileOpen(
		"C:\\Temp\\screenshot.bmp",
		co.FOPEN_RW_OPEN_OR_CREATE,
	)
	defer fout.Close()

	_, _ = fout.Write(bfh.Serialize())
	_, _ = fout.Write(bi.BmiHeader.Serialize())
	_, _ = fout.Write(bmpSlice)
}
Component Object Model (COM)

Component Object Model (COM)

Windigo has full support for C++ COM objects. The cleanup is performed by a win.OleReleaser object, which calls Release on multiple COM objects at once, much like an arena allocator. Every function which produces a COM object requires a win.OleReleaser to take care of its lifetime.

The example below uses COM objects to display the system native Open File window:

package main

import (
	"github.com/rodrigocfd/windigo/co"
	"github.com/rodrigocfd/windigo/win"
)

func main() {
	runtime.LockOSThread() // important: Windows GUI is single-threaded

	_, _ := win.CoInitializeEx(
		co.COINIT_APARTMENTTHREADED | co.COINIT_DISABLE_OLE1DDE)
	defer win.CoUninitialize()

	releaser := win.NewOleReleaser() // will release all COM objects created here
	defer releaser.Release()

	var fod *win.IFileOpenDialog
	_ = win.CoCreateInstance(
		releaser,
		co.CLSID_FileOpenDialog,
		nil,
		co.CLSCTX_INPROC_SERVER,
		&fod,
	)

	defOpts, _ := fod.GetOptions()
	_ = fod.SetOptions(defOpts |
		co.FOS_FORCEFILESYSTEM |
		co.FOS_FILEMUSTEXIST,
	)

	_ = fod.SetFileTypes([]win.COMDLG_FILTERSPEC{
		{Name: "Text files", Spec: "*.txt"},
		{Name: "All files", Spec: "*.*"},
	})
	_ = fod.SetFileTypeIndex(1)

	if ok, _ := fod.Show(win.HWND(0)); ok { // in real applications, pass the parent HWND
		item, _ := fod.GetResult(releaser)
		fileName, _ := item.GetDisplayName(co.SIGDN_FILESYSPATH)
		println(fileName)
	}
}
COM Automation

COM Automation

Windigo has bindings to IDispatch COM interface and VARIANT parameters, allowing you to invoke Automation methods.

The example below manipulates an Excel spreadsheet, saving a copy of it:

package main

import (
	"github.com/rodrigocfd/windigo/co"
	"github.com/rodrigocfd/windigo/win"
)

func main() {
	_, _ = win.CoInitializeEx(
		co.COINIT_APARTMENTTHREADED | co.COINIT_DISABLE_OLE1DDE)
	defer win.CoUninitialize()

	rel := win.NewOleReleaser()
	defer rel.Release()

	clsId, _ := win.CLSIDFromProgID("Excel.Application")

	var excel *win.IDispatch
	_ = win.CoCreateInstance(
		rel,
		clsId,
		nil,
		co.CLSCTX_LOCAL_SERVER,
		&excel,
	)

	books, _ := excel.InvokeGetIDispatch(rel, "Workbooks")
	file, _ := books.InvokeMethodIDispatch(rel, "Open", "C:\\Temp\\foo.xlsx")
	_, _ = file.InvokeMethod(rel, "SaveAs", "C:\\Temp\\foo copy.xlsx")
	_, _ = file.InvokeMethod(rel, "Close")
}

Architecture

The library is divided in four packages:

Package Description
co Native Win32 constants, all typed.
ui High-level UI windows and controls.
win Native Win32 structs, handles and functions.
wstr String and UTF-16 wide string management.

Package dependency:

flowchart BT
    internal/utl([internal/utl]) --> co
    ui --> win
    win --> internal/dll([internal/dll])
    win --> internal/utl
    win --> wstr
Loading

License

Licensed under MIT license, see LICENSE.md for details.

About

Windows API and GUI in idiomatic Go.

https://pkg.go.dev/github.com/rodrigocfd/windigo

License:MIT License


Languages

Language:Go 100.0%