detached 9a0b0c5

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 VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +33 -6
M internal/bar/bar.go +33 -6
@@ -17,6 +17,7 @@ import (
1717 "path/filepath"
1818 "strings"
1919 "syscall"
20+ "time"
2021
2122 "github.com/bnema/neferclient"
2223
@@ -78,9 +79,16 @@ type Bar struct {
7879 modCh chan struct{}
7980 cfgPath string
8081 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
8492 }
8593
8694 // New builds a bar from a validated config.
@@ -232,6 +240,9 @@ func (b *Bar) Run(ctx context.Context) (err error) {
232240 b.Stats.ModuleWake++
233241 case next := <-b.cfgCh:
234242 b.applyConfig(next)
243+ case <-b.settleC:
244+ b.settleC, b.settling = nil, false
245+ b.reconcile()
235246 }
236247 if b.fatal != nil {
237248 return b.fatal
@@ -315,7 +326,7 @@ func (b *Bar) step() error {
315326 }
316327
317328 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 {
319330 return nil
320331 }
321332 ok, err := b.rend.Draw(b.lay.Compose(), b.bg, &b.frame)
@@ -506,6 +517,22 @@ func (b *Bar) shutdown() error {
506517 return errors.Join(errs...)
507518 }
508519
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+
509536 // Handler methods. Events of a replaced surface are ignored.
510537
511538 // Configure marks the surface presentable and rebuilds as needed.
@@ -517,13 +544,13 @@ func (b *Bar) Configure(id neferclient.SurfaceID, _, _ int32) {
517544 b.canPresent = true
518545 }
519546 b.configured = true
520- b.reconcile()
547+ b.geometryChanged()
521548 }
522549
523550 // Scale follows the monitor's preferred scale.
524551 func (b *Bar) Scale(id neferclient.SurfaceID, _ float64) {
525552 if id == b.sid {
526- b.reconcile()
553+ b.geometryChanged()
527554 }
528555 }
529556
@@ -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 string80 cfgPath string
80 cfgCh chan config.Config81 cfgCh chan config.Config
81- Stats Stats82+
82- fatal error83+ // settling is true between a scale or size change and the moment both the
83- closed bool84+ // 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.fatal248 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 nil330 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 = true544 b.canPresent = true
518 }545 }
519 b.configured = true546 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

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 5

Tags 4

↑↓ moveenter openesc close