fix(bar): wait for both the new scale and the new width before rebuilding the renderer
- commit
- 9a0b0c56ad2ea9680125aea77e9459ca4a880bfa
- author
- brice · · 2026-10-05 15:10 UTC
- parent
- 5b114e9
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
1 file · +33 -6
M internal/bar/bar.go +33 -6
| @@ -17,6 +17,7 @@ import ( | |||
| 17 | 17 | "path/filepath" | |
| 18 | 18 | "strings" | |
| 19 | 19 | "syscall" | |
| 20 | + | "time" | |
| 20 | 21 | ||
| 21 | 22 | "github.com/bnema/neferclient" | |
| 22 | 23 | ||
| @@ -78,9 +79,16 @@ type Bar struct { | |||
| 78 | 79 | modCh chan struct{} | |
| 79 | 80 | cfgPath string | |
| 80 | 81 | cfgCh chan config.Config | |
| 81 | - | Stats Stats | |
| 82 | - | fatal error | |
| 83 | - | closed bool | |
| 82 | + | ||
| 83 | + | // settling is true between a scale or size change and the moment both the | |
| 84 | + | // new scale and the new logical width have arrived. They come as separate | |
| 85 | + | // events, in either order; building a renderer from half of them presents a | |
| 86 | + | // buffer of the wrong size, and some compositors keep that size. | |
| 87 | + | settling bool | |
| 88 | + | settleC <-chan time.Time | |
| 89 | + | Stats Stats | |
| 90 | + | fatal error | |
| 91 | + | closed bool | |
| 84 | 92 | } | |
| 85 | 93 | ||
| 86 | 94 | // New builds a bar from a validated config. | |
| @@ -232,6 +240,9 @@ func (b *Bar) Run(ctx context.Context) (err error) { | |||
| 232 | 240 | b.Stats.ModuleWake++ | |
| 233 | 241 | case next := <-b.cfgCh: | |
| 234 | 242 | b.applyConfig(next) | |
| 243 | + | case <-b.settleC: | |
| 244 | + | b.settleC, b.settling = nil, false | |
| 245 | + | b.reconcile() | |
| 235 | 246 | } | |
| 236 | 247 | if b.fatal != nil { | |
| 237 | 248 | return b.fatal | |
| @@ -315,7 +326,7 @@ func (b *Bar) step() error { | |||
| 315 | 326 | } | |
| 316 | 327 | ||
| 317 | 328 | func (b *Bar) draw() error { | |
| 318 | - | if !b.dirty || !b.canPresent || b.rend == nil { | |
| 329 | + | if !b.dirty || !b.canPresent || b.rend == nil || b.settling { | |
| 319 | 330 | return nil | |
| 320 | 331 | } | |
| 321 | 332 | ok, err := b.rend.Draw(b.lay.Compose(), b.bg, &b.frame) | |
| @@ -506,6 +517,22 @@ func (b *Bar) shutdown() error { | |||
| 506 | 517 | return errors.Join(errs...) | |
| 507 | 518 | } | |
| 508 | 519 | ||
| 520 | + | // settleDelay is how long the bar waits for the second half of a scale or size | |
| 521 | + | // change before it rebuilds. | |
| 522 | + | const settleDelay = 60 * time.Millisecond | |
| 523 | + | ||
| 524 | + | // geometryChanged is called when the compositor reports a new scale or size. | |
| 525 | + | // The first report builds at once, because there is nothing on screen yet. | |
| 526 | + | // Later ones wait for the dust to settle, and nothing is drawn meanwhile. | |
| 527 | + | func (b *Bar) geometryChanged() { | |
| 528 | + | if b.rend == nil { | |
| 529 | + | b.reconcile() | |
| 530 | + | return | |
| 531 | + | } | |
| 532 | + | b.settling = true | |
| 533 | + | b.settleC = time.After(settleDelay) | |
| 534 | + | } | |
| 535 | + | ||
| 509 | 536 | // Handler methods. Events of a replaced surface are ignored. | |
| 510 | 537 | ||
| 511 | 538 | // Configure marks the surface presentable and rebuilds as needed. | |
| @@ -517,13 +544,13 @@ func (b *Bar) Configure(id neferclient.SurfaceID, _, _ int32) { | |||
| 517 | 544 | b.canPresent = true | |
| 518 | 545 | } | |
| 519 | 546 | b.configured = true | |
| 520 | - | b.reconcile() | |
| 547 | + | b.geometryChanged() | |
| 521 | 548 | } | |
| 522 | 549 | ||
| 523 | 550 | // Scale follows the monitor's preferred scale. | |
| 524 | 551 | func (b *Bar) Scale(id neferclient.SurfaceID, _ float64) { | |
| 525 | 552 | if id == b.sid { | |
| 526 | - | b.reconcile() | |
| 553 | + | b.geometryChanged() | |
| 527 | 554 | } | |
| 528 | 555 | } | |
| 529 | 556 | ||
| @@ -17,6 +17,7 @@ import ( | |||||
| 17 | "path/filepath" | 17 | "path/filepath" | ||
| 18 | "strings" | 18 | "strings" | ||
| 19 | "syscall" | 19 | "syscall" | ||
| 20 | + | "time" | |||
| 20 | 21 | ||||
| 21 | "github.com/bnema/neferclient" | 22 | "github.com/bnema/neferclient" | ||
| 22 | 23 | ||||
| @@ -78,9 +79,16 @@ type Bar struct { | |||||
| 78 | modCh chan struct{} | 79 | modCh chan struct{} | ||
| 79 | cfgPath string | 80 | cfgPath string | ||
| 80 | cfgCh chan config.Config | 81 | cfgCh chan config.Config | ||
| 81 | - | Stats Stats | 82 | + | |
| 82 | - | fatal error | 83 | + | // settling is true between a scale or size change and the moment both the |
| 83 | - | closed bool | 84 | + | // new scale and the new logical width have arrived. They come as separate |
| 85 | + | // events, in either order; building a renderer from half of them presents a | |||
| 86 | + | // buffer of the wrong size, and some compositors keep that size. | |||
| 87 | + | settling bool | |||
| 88 | + | settleC <-chan time.Time | |||
| 89 | + | Stats Stats | |||
| 90 | + | fatal error | |||
| 91 | + | closed bool | |||
| 84 | } | 92 | } | ||
| 85 | 93 | ||||
| 86 | // New builds a bar from a validated config. | 94 | // New builds a bar from a validated config. | ||
| @@ -232,6 +240,9 @@ func (b *Bar) Run(ctx context.Context) (err error) { | |||||
| 232 | b.Stats.ModuleWake++ | 240 | b.Stats.ModuleWake++ | ||
| 233 | case next := <-b.cfgCh: | 241 | case next := <-b.cfgCh: | ||
| 234 | b.applyConfig(next) | 242 | b.applyConfig(next) | ||
| 243 | + | case <-b.settleC: | |||
| 244 | + | b.settleC, b.settling = nil, false | |||
| 245 | + | b.reconcile() | |||
| 235 | } | 246 | } | ||
| 236 | if b.fatal != nil { | 247 | if b.fatal != nil { | ||
| 237 | return b.fatal | 248 | return b.fatal | ||
| @@ -315,7 +326,7 @@ func (b *Bar) step() error { | |||||
| 315 | } | 326 | } | ||
| 316 | 327 | ||||
| 317 | func (b *Bar) draw() error { | 328 | func (b *Bar) draw() error { | ||
| 318 | - | if !b.dirty || !b.canPresent || b.rend == nil { | 329 | + | if !b.dirty || !b.canPresent || b.rend == nil || b.settling { |
| 319 | return nil | 330 | return nil | ||
| 320 | } | 331 | } | ||
| 321 | ok, err := b.rend.Draw(b.lay.Compose(), b.bg, &b.frame) | 332 | ok, err := b.rend.Draw(b.lay.Compose(), b.bg, &b.frame) | ||
| @@ -506,6 +517,22 @@ func (b *Bar) shutdown() error { | |||||
| 506 | return errors.Join(errs...) | 517 | return errors.Join(errs...) | ||
| 507 | } | 518 | } | ||
| 508 | 519 | ||||
| 520 | + | // settleDelay is how long the bar waits for the second half of a scale or size | |||
| 521 | + | // change before it rebuilds. | |||
| 522 | + | const settleDelay = 60 * time.Millisecond | |||
| 523 | + | ||||
| 524 | + | // geometryChanged is called when the compositor reports a new scale or size. | |||
| 525 | + | // The first report builds at once, because there is nothing on screen yet. | |||
| 526 | + | // Later ones wait for the dust to settle, and nothing is drawn meanwhile. | |||
| 527 | + | func (b *Bar) geometryChanged() { | |||
| 528 | + | if b.rend == nil { | |||
| 529 | + | b.reconcile() | |||
| 530 | + | return | |||
| 531 | + | } | |||
| 532 | + | b.settling = true | |||
| 533 | + | b.settleC = time.After(settleDelay) | |||
| 534 | + | } | |||
| 535 | + | ||||
| 509 | // Handler methods. Events of a replaced surface are ignored. | 536 | // Handler methods. Events of a replaced surface are ignored. | ||
| 510 | 537 | ||||
| 511 | // Configure marks the surface presentable and rebuilds as needed. | 538 | // Configure marks the surface presentable and rebuilds as needed. | ||
| @@ -517,13 +544,13 @@ func (b *Bar) Configure(id neferclient.SurfaceID, _, _ int32) { | |||||
| 517 | b.canPresent = true | 544 | b.canPresent = true | ||
| 518 | } | 545 | } | ||
| 519 | b.configured = true | 546 | b.configured = true | ||
| 520 | - | b.reconcile() | 547 | + | b.geometryChanged() |
| 521 | } | 548 | } | ||
| 522 | 549 | ||||
| 523 | // Scale follows the monitor's preferred scale. | 550 | // Scale follows the monitor's preferred scale. | ||
| 524 | func (b *Bar) Scale(id neferclient.SurfaceID, _ float64) { | 551 | func (b *Bar) Scale(id neferclient.SurfaceID, _ float64) { | ||
| 525 | if id == b.sid { | 552 | if id == b.sid { | ||
| 526 | - | b.reconcile() | 553 | + | b.geometryChanged() |
| 527 | } | 554 | } | ||
| 528 | } | 555 | } | ||
| 529 | 556 | ||||