~jackmordaunt

Australia

https://jackmordaunt.srht.site

Software Developer.

Trackers

~jackmordaunt/gopack

Last active 3 years ago

~jackmordaunt/gio-planet

Last active 3 years ago

~jackmordaunt/kanban

Last active 4 years ago

~jackmordaunt/Avisha

Last active 4 years ago

#605 gtx.Disabled() breaks all animations (InvalidateCmd doesn't execute) 9 months ago

Ticket created by ~jackmordaunt on ~eliasnaur/gio

This might very well be intended behaviour, however it doesn't match prior versions of Gio. If it's intended feel free to close the ticket.

In short: gtx.Disabled() has typically been used to block user input. However in the current version of Gio it will block all animations as well, since gtx.Execute(op.InvalidateCmd{}) is not processed on a disabled gtx.

Here's a minimal reproducer:

package main

import (
	"log"
	"os"

	"gioui.org/app"
	"gioui.org/layout"
	"gioui.org/op"
	"gioui.org/widget/material"
)

func main() {
	go func() {
		w := new(app.Window)
		w.Option(app.Title("playground"))

		if err := run(w); err != nil {
			log.Fatal(err)
		}

		os.Exit(0)
	}()

	app.Main()
}

func run(w *app.Window) error {
	var (
		th  = material.NewTheme()
		ops op.Ops
	)
	for {
		switch ev := w.Event().(type) {
		case app.DestroyEvent:
			return ev.Err
		case app.FrameEvent:
			gtx := app.NewContext(&ops, ev)
			frame(gtx, th)
			ev.Frame(gtx.Ops)
		}
	}
}

type (
	C = layout.Context
	D = layout.Dimensions
)

func frame(gtx C, th *material.Theme) D {
	return layout.Center.Layout(gtx, func(gtx C) D {
		gtx = gtx.Disabled()
		return material.Loader(th).Layout(gtx)
	})
}

#601 [Windows] system.ActionMove and gesture.Click appear to be mutually exclusive 9 months ago

Ticket created by ~jackmordaunt on ~eliasnaur/gio

After https://todo.sr.ht/~eliasnaur/gio/600 has been fixed (thanks!), I've run into another bug.

My goal is to have a custom decorated window that allows for both moving the window with a drag (using system.ActionMove) and double-click to (un)maximize the window.

To that end I have a widget.Decorations which implements the dragging, and expanded atop that I have a gesture.Click.

If pass-through mode is active, only the decoration dragging works and double-clicks do nothing.

If pass-through mode is inactive, only the double-click logic works and the window cannot be dragged.

It seems that system.ActionMove doesn't respect pass-through mode.

Here's a minimal reproducer:

package main

import (
	"fmt"
	"log"
	"os"

	"gioui.org/app"
	"gioui.org/gesture"
	"gioui.org/io/pointer"
	"gioui.org/io/system"
	"gioui.org/layout"
	"gioui.org/op"
	"gioui.org/op/clip"
	"gioui.org/widget"
	"gioui.org/widget/material"
)

func main() {
	go func() {
		window := new(app.Window)
		err := run(window)
		if err != nil {
			log.Fatal(err)
		}
		os.Exit(0)
	}()
	app.Main()
}

type (
	C = layout.Context
	D = layout.Dimensions
)

func run(window *app.Window) error {
	window.Option(app.Decorated(false))

	var (
		theme       = material.NewTheme()
		maximized   bool
		click       gesture.Click
		decorations widget.Decorations
		ops         op.Ops
	)

	for {
		switch e := window.Event().(type) {
		case app.DestroyEvent:
			return e.Err
		case app.FrameEvent:
			gtx := app.NewContext(&ops, e)
			layout.Flex{Axis: layout.Vertical}.Layout(gtx,
				layout.Rigid(func(gtx C) D {
					return layout.Stack{}.Layout(gtx,
						layout.Stacked(func(gtx C) D {
							return material.Decorations(theme, &decorations, system.ActionClose|system.ActionMaximize|system.ActionMinimize, "hello").Layout(gtx)
						}),
						layout.Expanded(func(gtx C) D {
							defer clip.Rect{Max: gtx.Constraints.Min}.Push(gtx.Ops).Pop()

							// If pass-through mode is active, only decoration dragging works.
							// If pass-through mode is disabled, only double-click logic works.
							defer pointer.PassOp{}.Push(gtx.Ops).Pop()

							click.Add(gtx.Ops)

							for {
								ev, ok := click.Update(gtx.Source)
								if !ok {
									break
								}
								if ev.Kind == gesture.KindClick && ev.NumClicks == 2 {
									if maximized {
										window.Perform(system.ActionUnmaximize)
										decorations.Perform(system.ActionUnmaximize)
									} else {
										window.Perform(system.ActionMaximize)
										decorations.Perform(system.ActionMaximize)
									}
									maximized = !maximized
								}
							}

							return D{Size: gtx.Constraints.Min}
						}),
					)
				}),
				layout.Rigid(func(gtx C) D {
					return layout.Center.Layout(gtx, func(gtx C) D {
						return material.H4(theme, fmt.Sprintf("Is Maximized? %v", decorations.Maximized())).Layout(gtx)
					})
				}),
			)
			e.Frame(gtx.Ops)
		}
	}
}

#600 [Windows] bad double-click handling for widget.Decorations 9 months ago

Comment by ~jackmordaunt on ~eliasnaur/gio

Environment: Windows 11, Go 1.22.4, Gio 0.7.0 (but also occurred on earlier versions eg 0.5.0).

#600 [Windows] bad double-click handling for widget.Decorations 9 months ago

Ticket created by ~jackmordaunt on ~eliasnaur/gio

Laying out widget.Decorations will produce a window that will maximize on double-click.

The problem is that it won't un-maximize from there, and the widget.Decorations reflects the incorrect state, e.g. Maximized() will return false.

This is breaking apps that are trying to have their own double-click handling because the very first double-click is swallowed by this mechanism and not reflected in the state.

Here's a minimal reproducer:

package main

import (
	"fmt"
	"log"
	"os"

	"gioui.org/app"
	"gioui.org/layout"
	"gioui.org/op"
	"gioui.org/widget"
	"gioui.org/widget/material"
)

func main() {
	go func() {
		window := new(app.Window)
		err := run(window)
		if err != nil {
			log.Fatal(err)
		}
		os.Exit(0)
	}()
	app.Main()
}

type (
	C = layout.Context
	D = layout.Dimensions
)

func run(window *app.Window) error {
	window.Option(app.Decorated(false))
	theme := material.NewTheme()
	var decorations widget.Decorations
	var ops op.Ops
	for {
		switch e := window.Event().(type) {
		case app.DestroyEvent:
			return e.Err
		case app.FrameEvent:
			gtx := app.NewContext(&ops, e)
			layout.Flex{Axis: layout.Vertical}.Layout(gtx,
				layout.Rigid(func(gtx C) D {
					return material.Decorations(theme, &decorations, 0, "hello").Layout(gtx)
				}),
				layout.Rigid(func(gtx C) D {
					return layout.Center.Layout(gtx, func(gtx C) D {
						return material.H4(theme, fmt.Sprintf("Is Maximized? %v", decorations.Maximized())).Layout(gtx)
					})
				}),
			)
			e.Frame(gtx.Ops)
		}
	}
}

Image

Image

#543 [Windows] widnow based race condition detected 1 year, 6 months ago

Comment by ~jackmordaunt on ~eliasnaur/gio

Curiously, this coincided with closing the window.

#543 [Windows] widnow based race condition detected 1 year, 6 months ago

Ticket created by ~jackmordaunt on ~eliasnaur/gio

It appears there is a race between app.window.Wakeup() and app.windowProc().

==================
WARNING: DATA RACE
Read at 0x00c00aa88c80 by goroutine 4225:
  gioui.org/app.(*window).Wakeup()
      C:/Users/jackm/go/pkg/mod/gioui.org@v0.3.1-0.20230918142811-27193ae8e816/app/os_windows.go:605 +0x47
  gioui.org/app.driver.Wakeup-fm()
      <autogenerated>:1 +0x49
  gioui.org/app.(*Window).run()
      C:/Users/jackm/go/pkg/mod/gioui.org@v0.3.1-0.20230918142811-27193ae8e816/app/window.go:976 +0x549
  gioui.org/app.NewWindow.func1()
      C:/Users/jackm/go/pkg/mod/gioui.org@v0.3.1-0.20230918142811-27193ae8e816/app/window.go:188 +0x84

Previous write at 0x00c00aa88c80 by goroutine 4228:
  gioui.org/app.windowProc()
      C:/Users/jackm/go/pkg/mod/gioui.org@v0.3.1-0.20230918142811-27193ae8e816/app/os_windows.go:310 +0x386
  runtime.call32()
      C:/Program Files/Go/src/runtime/asm_amd64.s:748 +0x47
  golang.org/x/sys/windows.(*LazyProc).Call()
      C:/Users/jackm/go/pkg/mod/golang.org/x/sys@v0.12.0/windows/dll_windows.go:348 +0xe4
  gioui.org/app/internal/windows.DefWindowProc()
      C:/Users/jackm/go/pkg/mod/gioui.org@v0.3.1-0.20230918142811-27193ae8e816/app/internal/windows/windows.go:448 +0x1b3
  gioui.org/app.windowProc()
      C:/Users/jackm/go/pkg/mod/gioui.org@v0.3.1-0.20230918142811-27193ae8e816/app/os_windows.go:437 +0x2ded
  runtime.call32()
      C:/Program Files/Go/src/runtime/asm_amd64.s:748 +0x47
  golang.org/x/sys/windows.(*LazyProc).Call()
      C:/Users/jackm/go/pkg/mod/golang.org/x/sys@v0.12.0/windows/dll_windows.go:348 +0xe4
  gioui.org/app/internal/windows.DispatchMessage()
      C:/Users/jackm/go/pkg/mod/gioui.org@v0.3.1-0.20230918142811-27193ae8e816/app/internal/windows/windows.go:457 +0xd1
  gioui.org/app.(*window).loop()
      C:/Users/jackm/go/pkg/mod/gioui.org@v0.3.1-0.20230918142811-27193ae8e816/app/os_windows.go:584 +0x224
  gioui.org/app.newWindow.func1()
      C:/Users/jackm/go/pkg/mod/gioui.org@v0.3.1-0.20230918142811-27193ae8e816/app/os_windows.go:112 +0x632

Goroutine 4225 (running) created at:
  gioui.org/app.NewWindow()
      C:/Users/jackm/go/pkg/mod/gioui.org@v0.3.1-0.20230918142811-27193ae8e816/app/window.go:188 +0x16b6
  git.sr.ht/~gioverse/skel/window.Windower.Run()
      C:/Users/jackm/go/pkg/mod/git.sr.ht/~gioverse/skel@v0.0.0-20230919201906-8a759b754d85/window/windower.go:118 +0x6c9
  main.runClient()
      C:/Users/jackm/Source/desktop/cmd/client/main.go:315 +0x39a4
  main.main.func1()
      C:/Users/jackm/Source/desktop/cmd/client/main.go:114 +0x104

Goroutine 4228 (running) created at:
  gioui.org/app.newWindow()
      C:/Users/jackm/go/pkg/mod/gioui.org@v0.3.1-0.20230918142811-27193ae8e816/app/os_windows.go:89 +0x1ef
  gioui.org/app.(*Window).run()
      C:/Users/jackm/go/pkg/mod/gioui.org@v0.3.1-0.20230918142811-27193ae8e816/app/window.go:942 +0xa4
  gioui.org/app.NewWindow.func1()
      C:/Users/jackm/go/pkg/mod/gioui.org@v0.3.1-0.20230918142811-27193ae8e816/app/window.go:188 +0x84
==================

#497 [windows] panic: send on closed channel (potential race) 1 year, 11 months ago

Comment by ~jackmordaunt on ~eliasnaur/gio

Excellent, changes look good! Thanks Elias.

#497 [windows] panic: send on closed channel (potential race) 1 year, 11 months ago

Ticket created by ~jackmordaunt on ~eliasnaur/gio

I believe a race condition causing a send on closed channel that gets triggered when validateAndProcess returns an error and processEvent fails to wait for the w.dead channel to close before processing another frame event.

panic: send on closed channel

goroutine 57 [running, locked to thread]:
gioui.org/app.(*Window).processEvent(0xc000438000, {0x7ff7088881d0, 0xc00007ef00}, {0x7ff708876a60, 0xc008621980})
        gioui.org@v0.0.0-20230425023356-bba91263b077/app/window.go:879 +0x987
gioui.org/app.(*callbacks).Event(0xc0004384d8, {0x7ff708876a60, 0xc008621980})
        gioui.org@v0.0.0-20230425023356-bba91263b077/app/window.go:484 +0x2d2
gioui.org/app.(*window).draw(0xc00007ef00, 0x0)
        gioui.org@v0.0.0-20230425023356-bba91263b077/app/os_windows.go:604 +0x17c
gioui.org/app.(*window).loop(0xc00007ef00)
        gioui.org@v0.0.0-20230425023356-bba91263b077/app/os_windows.go:554 +0x89
gioui.org/app.newWindow.func1()
        gioui.org@v0.0.0-20230425023356-bba91263b077/app/os_windows.go:118 +0x386
created by gioui.org/app.newWindow
        gioui.org@v0.0.0-20230425023356-bba91263b077/app/os_windows.go:95 +0x105

Thread 1.

// w.processEvent(...)
if err := w.validateAndProcess(d, viewSize, e2.Sync, wrapper, signal); err != nil {
	w.destroyGPU()
	w.out <- system.DestroyEvent{Err: err}
	close(w.out)
	w.destroy <- struct{}{} // We falsely assume that this send means `w.dead` will be closed before next time we enter `processEvent`.
	break
}

Thread 2.

// w.run()
case <-w.destroy:
        // Between the receive operation and the close operation is non-zero time wherein thread 1 can re-enter `processEvent` before we actually execute this channel close. 
	close(w.dead)

Here is some execution flow to help explain:

Thread 2 (blocked): case <-w.destroy
Thread 1 (running): processEvent(system.FrameEvent)
Thread 1 (running): validateAndProcess(...) -> error // oops, got error
Thread 1 (running): w.out <- system.DestroyEvent
Thread 1 (running): close(w.out)
Thread 1 (running): w.destroy <- struct{}{}
Thread 2 (receive): case <-w.destroy
Thread 1 (running): break
Thread 1 (running): processEvent(system.FrameEvent)
Thread 1 (running): <-w.dead  // not dead yet!
Thread 2 (running): close(w.dead) // finally closed, but too late to protect us
Thread 1 (running): w.out <- e2.FrameEvent // panic!!

processEvent assumes that once it passes the dead check it can safely send on the out channel. In this case, we pass the dead check just before the dead channel is closed. Thus we continue processing the frame event under the false the assumption that the out channel is open, so we send on it causing a panic.

#8 [skel] handle panic in skel worker functions 2 years ago

Ticket created by ~jackmordaunt on ~gioverse/chat

At the moment, panics are silently recovered. This means that messages are never delivered and the application has no way of detecting it.

We need to surface a way to handle panics inside workers.

In the case of a future, the panic can propagate to the handler as a plain error "panic processing worker: %w", for example.

#413 [bug] Windows: crash closing custom render window 2 years ago

Comment by ~jackmordaunt on ~eliasnaur/gio

Thanks for the tip Elias! I will investigate further based on this.