Overhaul of package app

The app-overhaul branch, built on top of #550, is a proposal for several changes to package app.

Please test your your programs on available platforms. If you encounter problems, please report your exact system configuration and a minimal reproducer. If you're able, I would appreciate you git bisect to determine the first failing commit.

To fetch the changes,

$ go get gioui.org@app-overhaul

Below is a description of the changes.

#Removal of Main

The requirement to call app.Main in your main goroutine was caused by the macOS platform needing control of the main thread. I managed to work around this limitation, leading to the removal of app.Main.

Note that at least one Window is required to have its Event method called on the main goroutine to avoid deadlocks on macOS.

See also my Go proposal for a more robust solution to the issues on macOS, as well as a way towards go run for iOS and the merging of the maOS and iOS backends.

#Window.NextEvent renamed to Event

Window.Event now matches #550's Source.Event.

#Refactor of backends

A major refactor pushes the window event loop to backends.

Notably, this simplifies the Wayland, X11, WASM platforms that no longer need a dedicated goroutine for each window. Every WIndow is now driven directly by the Window.Event method.

#StageEvent and Stage removed

StageEvent served only redundant purposes:

  • To detect whether the window has focus. That is covered by key.FocusEvent.
  • To detect whether the window is currently visible. That is covered by the absence or presence of FrameEvents.
  • To detect when the window native handle is valid. That is covered by ViewEvent.

#NewWindow replaced by the zero-value Window

The GUI window is created and shown by the first call to a Window method. The options passed to NewWindow can instead be passed to Window.Option.

Assigned to
6 months ago
3 months ago
No labels applied.

~beikege 6 months ago

windows 11
go get gioui.org@app-overhaul


Windowed -> Fullscreen

Fullscreen -> Windowed

When switching from fullscreen to windowed mode, the window size becomes maximized. Is this the intended behavior?

~egonelbre 6 months ago

I'm not quite convinced that you can easily and reliably detect StageEvent-s from the presence of FrameEvents. e.g. You have a separate audio goroutine that should be paused when the app is minimized, then it's not obvious whether the app isn't getting events due to not needing to render anything or due to being minimized.

~eliasnaur 6 months ago

~egonelbre: note that we have the app.Minimized WindowMode already. More importantly, platforms such as Wayland won't ever tell you whether a window is minimized, so your use-case seems better triggered by, say, the loss of window focus.

~eliasnaur 6 months ago

~beikege: this was also a bug on main. I've fixed it and rebased the app-overhaul and event-filters branches. Use go get gioui.org@e6f8fcb3b027818 to fetch the newest version.

~beikege 6 months ago

~eliasnaur Thank you

~whereswaldon 6 months ago

I share ~egonelbre's concern that StageEvent filled a useful niche. I know one use-case for it was detecting focus loss in order to close the window. This was for an application launcher, which it makes sense to just close if the user clicks away from it. Another use case is wanting to play sound only when your window has focus. It is not clear to me how to tell the difference between "my window has focus" and "my window isn't being invalidated because there are no input events right now."

I also don't see how key.FocusEvent covers window focus tracking entirely. If I install a root-level key handler, it will get a focus event each time the focus changes between widgets, and I suppose it also gets one when the window loses focus, but I don't see any fields on key.FocusEvent that would differentiate between "focus transferred to another widget" and "focus transferred to another window."

Am I missing some invariants or side-channels that make the above issues moot?

~eliasnaur 6 months ago*

You're not missing anything. StageEvent conflated "visible enough for FrameEvents" and "focused", and the latter overlapped with the key.FocusEvent sent when backends detect focus changes. If we want to track window focus apart from widget focus, I suggest it be in the form of a app.Config field tracked by the usual app.ConfigEvent.

~whereswaldon 6 months ago

Okay, I'm fine with an app.Config field tracking whether the window is focused. I think that is a necessary change for existing Gio applications that were using the stage event as a proxy for focused-ness to migrate to this new API.

~eliasnaur 6 months ago

Alright, I've added Config.Focused and changed the backends to update that. As a nice side-effect, app.Window is now in charge of sending the appropriate key.FocusEvent.

~ajstarks 5 months ago*

System: Fedora 39, UM690 (32Gb RAM, AMD Ryzen™ 9 6900HX with Radeon™ Graphics × 16)

$ go version -m ./hello
./hello: go1.21.6
	path	github.com/ajstarks/giocanvas/hello
	mod	github.com/ajstarks/giocanvas	(devel)	
	dep	gioui.org	v0.4.2-0.20231221171002-cb8efefa839a	h1:QBZFhRotXHnB+Nq2iGfIZoinKbZRjEPTnA+9ReOj2LI=
	dep	gioui.org/cpu	v0.0.0-20210817075930-8d6a761490d2	h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc=
	dep	gioui.org/shader	v1.0.8	h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA=
	dep	github.com/go-text/typesetting	v0.0.0-20230803102845-24e03d8b5372	h1:FQivqchis6bE2/9uF70M2gmmLpe82esEm2QadL0TEJo=
	dep	golang.org/x/exp	v0.0.0-20221012211006-4de253d81b95	h1:sBdrWpxhGDdTAYNqbgBLAR+ULAPPhfgncLr1X0lyWtg=
	dep	golang.org/x/exp/shiny	v0.0.0-20220827204233-334a2380cb91	h1:ryT6Nf0R83ZgD8WnFFdfI8wCeyqgdXWN4+CkFVNPAT0=
	dep	golang.org/x/image	v0.14.0	h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
	dep	golang.org/x/sys	v0.5.0	h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
	dep	golang.org/x/text	v0.14.0	h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=

I converted all giocanvas clients to this new API, and all run fine on X11 as well as Mac OS (11.7.9, MacBook Pro 2.3 GHz Quad-Core Intel Core i7). If I switch to Wayland on Fedora, the same binary crashes: (See:


Here is the "hello" program that crashed:

// hello is the giocanvas hello, world
package main

import (


func main() {
	var cw, ch int
	flag.IntVar(&cw, "width", 1000, "canvas width")
	flag.IntVar(&ch, "height", 1000, "canvas height")

	width := float32(cw)
	height := float32(ch)

	w := &app.Window{}
	w.Option(app.Title("hello"), app.Size(unit.Dp(width), unit.Dp(height)))
	if err := hello(w, width, height); err != nil {
		io.WriteString(os.Stderr, "Cannot create the window\n")

func hello(w *app.Window, width, height float32) error {
	for {
		ev := w.Event()
		switch e := ev.(type) {
		case app.DestroyEvent:
			return e.Err
		case app.FrameEvent:
			canvas := giocanvas.NewCanvas(float32(e.Size.X), float32(e.Size.Y), app.FrameEvent{})
			canvas.CenterRect(50, 50, 100, 100, color.NRGBA{0, 0, 0, 255})
			canvas.Circle(50, 0, 50, color.NRGBA{0, 0, 255, 255})
			canvas.TextMid(50, 20, 10, "hello, world", color.NRGBA{255, 255, 255, 255})
			canvas.CenterImage("earth.jpg", 50, 70, 1000, 1000, 30)

~eliasnaur 4 months ago

Thank you for the report, ~ajstarks. I've fixed the issue on the branch.

I've also re-added app.Main, pending a decision on https://github.com/golang/go/issues/64755 .

~eliasnaur REPORTED IMPLEMENTED 3 months ago

This has been merged into main, except for the removal of app.Main.

Register here or Log in to comment, or comment via email.