fix(ticks): release split raw callback slots
- commit
- 7c3858b0c868bdc71d38a851521d02e9ba7c147e
- author
- brice · · 2026-07-14 07:11 UTC
- committer
- brice · · 2026-07-14 09:32 UTC
- parent
- 67cc805
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
1 file · +49 -7
M internal/ui/layout/gtk_widgets.go +49 -7
| @@ -1,7 +1,10 @@ | |||
| 1 | 1 | package layout | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "sync" | |
| 5 | + | ||
| 4 | 6 | "github.com/bnema/puregotk/v4/gdk" | |
| 7 | + | "github.com/bnema/puregotk/v4/glib" | |
| 5 | 8 | "github.com/bnema/puregotk/v4/gobject" | |
| 6 | 9 | "github.com/bnema/puregotk/v4/graphene" | |
| 7 | 10 | "github.com/bnema/puregotk/v4/gtk" | |
| @@ -82,8 +85,14 @@ func (w *gtkWidget) ComputePoint(target Widget) (x, y float64, ok bool) { | |||
| 82 | 85 | // gtkPaned wraps gtk.Paned to implement PanedWidget. | |
| 83 | 86 | type gtkPaned struct { | |
| 84 | 87 | inner *gtk.Paned | |
| 88 | + | ||
| 89 | + | tickMu sync.Mutex | |
| 90 | + | tickCallbacks map[uint]*gtk.TickCallback | |
| 85 | 91 | } | |
| 86 | 92 | ||
| 93 | + | // unrefTickCallback is the canonical puregotk/purego callback-slot release. | |
| 94 | + | var unrefTickCallback = glib.UnrefCallback | |
| 95 | + | ||
| 87 | 96 | func (p *gtkPaned) Show() { p.inner.Show() } | |
| 88 | 97 | func (p *gtkPaned) Hide() { p.inner.Hide() } | |
| 89 | 98 | func (p *gtkPaned) SetVisible(visible bool) { p.inner.SetVisible(visible) } | |
| @@ -176,14 +185,47 @@ func (p *gtkPaned) ConnectNotifyPosition(callback func()) uint { | |||
| 176 | 185 | } | |
| 177 | 186 | ||
| 178 | 187 | func (p *gtkPaned) AddTickCallback(callback func() bool) uint { | |
| 179 | - | cb := gtk.TickCallback(func(widget uintptr, frameClock uintptr, userData uintptr) bool { | |
| 180 | - | return callback() | |
| 181 | - | }) | |
| 182 | - | return p.inner.AddTickCallback(&cb, 0, nil) | |
| 188 | + | cb := new(gtk.TickCallback) | |
| 189 | + | var id uint | |
| 190 | + | *cb = func(_ uintptr, _ uintptr, _ uintptr) bool { | |
| 191 | + | keepRunning := callback() | |
| 192 | + | if !keepRunning { | |
| 193 | + | // GTK removes a false-returning source itself; release its matching | |
| 194 | + | // purego slot exactly once rather than merely dropping the Go pointer. | |
| 195 | + | p.releaseTickCallback(id) | |
| 196 | + | } | |
| 197 | + | return keepRunning | |
| 198 | + | } | |
| 199 | + | id = p.inner.AddTickCallback(cb, 0, nil) | |
| 200 | + | if id != 0 { | |
| 201 | + | p.tickMu.Lock() | |
| 202 | + | if p.tickCallbacks == nil { | |
| 203 | + | p.tickCallbacks = make(map[uint]*gtk.TickCallback) | |
| 204 | + | } | |
| 205 | + | p.tickCallbacks[id] = cb | |
| 206 | + | p.tickMu.Unlock() | |
| 207 | + | } | |
| 208 | + | return id | |
| 209 | + | } | |
| 210 | + | ||
| 211 | + | func (p *gtkPaned) RemoveTickCallback(id uint) { | |
| 212 | + | p.inner.RemoveTickCallback(id) | |
| 213 | + | p.releaseTickCallback(id) | |
| 214 | + | } | |
| 215 | + | ||
| 216 | + | func (p *gtkPaned) releaseTickCallback(id uint) { | |
| 217 | + | if id == 0 { | |
| 218 | + | return | |
| 219 | + | } | |
| 220 | + | p.tickMu.Lock() | |
| 221 | + | callback := p.tickCallbacks[id] | |
| 222 | + | delete(p.tickCallbacks, id) | |
| 223 | + | p.tickMu.Unlock() | |
| 224 | + | if callback != nil { | |
| 225 | + | _ = unrefTickCallback(callback) | |
| 226 | + | } | |
| 183 | 227 | } | |
| 184 | 228 | ||
| 185 | - | func (p *gtkPaned) RemoveTickCallback(id uint) { p.inner.RemoveTickCallback(id) } | |
| 186 | - | ||
| 187 | 229 | func (p *gtkPaned) GetAllocatedWidth() int { return p.inner.GetAllocatedWidth() } | |
| 188 | 230 | func (p *gtkPaned) GetAllocatedHeight() int { return p.inner.GetAllocatedHeight() } | |
| 189 | 231 | func (p *gtkPaned) AddController(c *gtk.EventController) { p.inner.AddController(c) } | |
| @@ -781,7 +823,7 @@ func (f *GtkWidgetFactory) NewPaned(orientation Orientation) PanedWidget { | |||
| 781 | 823 | paned := gtk.NewPaned(orientation) | |
| 782 | 824 | paned.SetHexpand(true) | |
| 783 | 825 | paned.SetVexpand(true) | |
| 784 | - | return >kPaned{inner: paned} | |
| 826 | + | return >kPaned{inner: paned, tickCallbacks: make(map[uint]*gtk.TickCallback)} | |
| 785 | 827 | } | |
| 786 | 828 | ||
| 787 | 829 | func (f *GtkWidgetFactory) NewBox(orientation Orientation, spacing int) BoxWidget { | |
| @@ -1,7 +1,10 @@ | |||||
| 1 | package layout | 1 | package layout | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "sync" | |||
| 5 | + | ||||
| 4 | "github.com/bnema/puregotk/v4/gdk" | 6 | "github.com/bnema/puregotk/v4/gdk" | ||
| 7 | + | "github.com/bnema/puregotk/v4/glib" | |||
| 5 | "github.com/bnema/puregotk/v4/gobject" | 8 | "github.com/bnema/puregotk/v4/gobject" | ||
| 6 | "github.com/bnema/puregotk/v4/graphene" | 9 | "github.com/bnema/puregotk/v4/graphene" | ||
| 7 | "github.com/bnema/puregotk/v4/gtk" | 10 | "github.com/bnema/puregotk/v4/gtk" | ||
| @@ -82,8 +85,14 @@ func (w *gtkWidget) ComputePoint(target Widget) (x, y float64, ok bool) { | |||||
| 82 | // gtkPaned wraps gtk.Paned to implement PanedWidget. | 85 | // gtkPaned wraps gtk.Paned to implement PanedWidget. | ||
| 83 | type gtkPaned struct { | 86 | type gtkPaned struct { | ||
| 84 | inner *gtk.Paned | 87 | inner *gtk.Paned | ||
| 88 | + | ||||
| 89 | + | tickMu sync.Mutex | |||
| 90 | + | tickCallbacks map[uint]*gtk.TickCallback | |||
| 85 | } | 91 | } | ||
| 86 | 92 | ||||
| 93 | + | // unrefTickCallback is the canonical puregotk/purego callback-slot release. | |||
| 94 | + | var unrefTickCallback = glib.UnrefCallback | |||
| 95 | + | ||||
| 87 | func (p *gtkPaned) Show() { p.inner.Show() } | 96 | func (p *gtkPaned) Show() { p.inner.Show() } | ||
| 88 | func (p *gtkPaned) Hide() { p.inner.Hide() } | 97 | func (p *gtkPaned) Hide() { p.inner.Hide() } | ||
| 89 | func (p *gtkPaned) SetVisible(visible bool) { p.inner.SetVisible(visible) } | 98 | func (p *gtkPaned) SetVisible(visible bool) { p.inner.SetVisible(visible) } | ||
| @@ -176,14 +185,47 @@ func (p *gtkPaned) ConnectNotifyPosition(callback func()) uint { | |||||
| 176 | } | 185 | } | ||
| 177 | 186 | ||||
| 178 | func (p *gtkPaned) AddTickCallback(callback func() bool) uint { | 187 | func (p *gtkPaned) AddTickCallback(callback func() bool) uint { | ||
| 179 | - | cb := gtk.TickCallback(func(widget uintptr, frameClock uintptr, userData uintptr) bool { | 188 | + | cb := new(gtk.TickCallback) |
| 180 | - | return callback() | 189 | + | var id uint |
| 181 | - | }) | 190 | + | *cb = func(_ uintptr, _ uintptr, _ uintptr) bool { |
| 182 | - | return p.inner.AddTickCallback(&cb, 0, nil) | 191 | + | keepRunning := callback() |
| 192 | + | if !keepRunning { | |||
| 193 | + | // GTK removes a false-returning source itself; release its matching | |||
| 194 | + | // purego slot exactly once rather than merely dropping the Go pointer. | |||
| 195 | + | p.releaseTickCallback(id) | |||
| 196 | + | } | |||
| 197 | + | return keepRunning | |||
| 198 | + | } | |||
| 199 | + | id = p.inner.AddTickCallback(cb, 0, nil) | |||
| 200 | + | if id != 0 { | |||
| 201 | + | p.tickMu.Lock() | |||
| 202 | + | if p.tickCallbacks == nil { | |||
| 203 | + | p.tickCallbacks = make(map[uint]*gtk.TickCallback) | |||
| 204 | + | } | |||
| 205 | + | p.tickCallbacks[id] = cb | |||
| 206 | + | p.tickMu.Unlock() | |||
| 207 | + | } | |||
| 208 | + | return id | |||
| 209 | + | } | |||
| 210 | + | ||||
| 211 | + | func (p *gtkPaned) RemoveTickCallback(id uint) { | |||
| 212 | + | p.inner.RemoveTickCallback(id) | |||
| 213 | + | p.releaseTickCallback(id) | |||
| 214 | + | } | |||
| 215 | + | ||||
| 216 | + | func (p *gtkPaned) releaseTickCallback(id uint) { | |||
| 217 | + | if id == 0 { | |||
| 218 | + | return | |||
| 219 | + | } | |||
| 220 | + | p.tickMu.Lock() | |||
| 221 | + | callback := p.tickCallbacks[id] | |||
| 222 | + | delete(p.tickCallbacks, id) | |||
| 223 | + | p.tickMu.Unlock() | |||
| 224 | + | if callback != nil { | |||
| 225 | + | _ = unrefTickCallback(callback) | |||
| 226 | + | } | |||
| 183 | } | 227 | } | ||
| 184 | 228 | ||||
| 185 | - | func (p *gtkPaned) RemoveTickCallback(id uint) { p.inner.RemoveTickCallback(id) } | |||
| 186 | - | ||||
| 187 | func (p *gtkPaned) GetAllocatedWidth() int { return p.inner.GetAllocatedWidth() } | 229 | func (p *gtkPaned) GetAllocatedWidth() int { return p.inner.GetAllocatedWidth() } | ||
| 188 | func (p *gtkPaned) GetAllocatedHeight() int { return p.inner.GetAllocatedHeight() } | 230 | func (p *gtkPaned) GetAllocatedHeight() int { return p.inner.GetAllocatedHeight() } | ||
| 189 | func (p *gtkPaned) AddController(c *gtk.EventController) { p.inner.AddController(c) } | 231 | func (p *gtkPaned) AddController(c *gtk.EventController) { p.inner.AddController(c) } | ||
| @@ -781,7 +823,7 @@ func (f *GtkWidgetFactory) NewPaned(orientation Orientation) PanedWidget { | |||||
| 781 | paned := gtk.NewPaned(orientation) | 823 | paned := gtk.NewPaned(orientation) | ||
| 782 | paned.SetHexpand(true) | 824 | paned.SetHexpand(true) | ||
| 783 | paned.SetVexpand(true) | 825 | paned.SetVexpand(true) | ||
| 784 | - | return >kPaned{inner: paned} | 826 | + | return >kPaned{inner: paned, tickCallbacks: make(map[uint]*gtk.TickCallback)} |
| 785 | } | 827 | } | ||
| 786 | 828 | ||||
| 787 | func (f *GtkWidgetFactory) NewBox(orientation Orientation, spacing int) BoxWidget { | 829 | func (f *GtkWidgetFactory) NewBox(orientation Orientation, spacing int) BoxWidget { | ||