fix(layer): only exclusive layers take keyboard focus; send leave on layer unmap
- commit
- 4c236465b0ab863606a4033d9f20dc79f891c0f3
- author
- brice · · 2026-09-24 09:46 UTC
- parent
- 6347722
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +9 -6
M internal/adapters/wayland/layer.go +3 -0
| @@ -123,6 +123,9 @@ func (l *layerSurface) unmap() { | |||
| 123 | 123 | if !l.mapped { | |
| 124 | 124 | return | |
| 125 | 125 | } | |
| 126 | + | if s := l.shell.server; s.focused == l.id { | |
| 127 | + | s.changeFocus(0) | |
| 128 | + | } | |
| 126 | 129 | l.mapped = false | |
| 127 | 130 | l.configured = false | |
| 128 | 131 | l.acked = false | |
| @@ -123,6 +123,9 @@ func (l *layerSurface) unmap() { | |||||
| 123 | if !l.mapped { | 123 | if !l.mapped { | ||
| 124 | return | 124 | return | ||
| 125 | } | 125 | } | ||
| 126 | + | if s := l.shell.server; s.focused == l.id { | |||
| 127 | + | s.changeFocus(0) | |||
| 128 | + | } | |||
| 126 | l.mapped = false | 129 | l.mapped = false | ||
| 127 | l.configured = false | 130 | l.configured = false | ||
| 128 | l.acked = false | 131 | l.acked = false | ||
M internal/core/core.go +4 -4
| @@ -161,13 +161,13 @@ func (c *Core) clientRect(p Placement) Rect { | |||
| 161 | 161 | return Rect{X: r.X + b, Y: r.Y + b, W: r.W - 2*b, H: r.H - 2*b} | |
| 162 | 162 | } | |
| 163 | 163 | ||
| 164 | - | // keyboardFocus is the newest mapped top/overlay layer that asks for the keyboard | |
| 165 | - | // (exclusive or on-demand), else the focused window. When the layer unmaps, | |
| 166 | - | // focus returns to the window. | |
| 164 | + | // keyboardFocus is the mapped top/overlay layer with exclusive keyboard | |
| 165 | + | // interactivity and the highest ID, else the focused window. On-demand layers | |
| 166 | + | // never take focus automatically. When the layer unmaps, focus returns to the window. | |
| 167 | 167 | func (c *Core) keyboardFocus() WindowID { | |
| 168 | 168 | var layer WindowID | |
| 169 | 169 | for _, l := range c.layers { | |
| 170 | - | if l.Keyboard != 0 && (l.Layer == ports.LayerTop || l.Layer == ports.LayerOverlay) && l.ID > layer { | |
| 170 | + | if l.Keyboard == 1 && (l.Layer == ports.LayerTop || l.Layer == ports.LayerOverlay) && l.ID > layer { | |
| 171 | 171 | layer = l.ID | |
| 172 | 172 | } | |
| 173 | 173 | } | |
| @@ -161,13 +161,13 @@ func (c *Core) clientRect(p Placement) Rect { | |||||
| 161 | return Rect{X: r.X + b, Y: r.Y + b, W: r.W - 2*b, H: r.H - 2*b} | 161 | return Rect{X: r.X + b, Y: r.Y + b, W: r.W - 2*b, H: r.H - 2*b} | ||
| 162 | } | 162 | } | ||
| 163 | 163 | ||||
| 164 | - | // keyboardFocus is the newest mapped top/overlay layer that asks for the keyboard | 164 | + | // keyboardFocus is the mapped top/overlay layer with exclusive keyboard |
| 165 | - | // (exclusive or on-demand), else the focused window. When the layer unmaps, | 165 | + | // interactivity and the highest ID, else the focused window. On-demand layers |
| 166 | - | // focus returns to the window. | 166 | + | // never take focus automatically. When the layer unmaps, focus returns to the window. |
| 167 | func (c *Core) keyboardFocus() WindowID { | 167 | func (c *Core) keyboardFocus() WindowID { | ||
| 168 | var layer WindowID | 168 | var layer WindowID | ||
| 169 | for _, l := range c.layers { | 169 | for _, l := range c.layers { | ||
| 170 | - | if l.Keyboard != 0 && (l.Layer == ports.LayerTop || l.Layer == ports.LayerOverlay) && l.ID > layer { | 170 | + | if l.Keyboard == 1 && (l.Layer == ports.LayerTop || l.Layer == ports.LayerOverlay) && l.ID > layer { |
| 171 | layer = l.ID | 171 | layer = l.ID | ||
| 172 | } | 172 | } | ||
| 173 | } | 173 | } | ||
M internal/core/core_test.go +2 -2
| @@ -312,9 +312,9 @@ func TestLayerKeyboardFocus(t *testing.T) { | |||
| 312 | 312 | if id := focusOf(); id != 1 { | |
| 313 | 313 | t.Fatalf("window focus %d", id) | |
| 314 | 314 | } | |
| 315 | - | // A bar without keyboard interactivity does not steal focus; a launcher does. | |
| 315 | + | // An on-demand bar does not steal focus; an exclusive launcher does. | |
| 316 | 316 | launcher := ports.LayerSurface{ID: 7, Layer: ports.LayerOverlay, Width: 40, Height: 20, Keyboard: 1} | |
| 317 | - | bar := ports.LayerSurface{ID: 6, Layer: ports.LayerTop, Anchor: ports.AnchorTop | ports.AnchorLeft | ports.AnchorRight, Height: 5} | |
| 317 | + | bar := ports.LayerSurface{ID: 6, Layer: ports.LayerTop, Anchor: ports.AnchorTop | ports.AnchorLeft | ports.AnchorRight, Height: 5, Keyboard: 2} | |
| 318 | 318 | client <- ports.LayerChanged{Layers: []ports.LayerSurface{bar, launcher}} | |
| 319 | 319 | receive(t, scenes) | |
| 320 | 320 | if id := focusOf(); id != 7 { | |
| @@ -312,9 +312,9 @@ func TestLayerKeyboardFocus(t *testing.T) { | |||||
| 312 | if id := focusOf(); id != 1 { | 312 | if id := focusOf(); id != 1 { | ||
| 313 | t.Fatalf("window focus %d", id) | 313 | t.Fatalf("window focus %d", id) | ||
| 314 | } | 314 | } | ||
| 315 | - | // A bar without keyboard interactivity does not steal focus; a launcher does. | 315 | + | // An on-demand bar does not steal focus; an exclusive launcher does. |
| 316 | launcher := ports.LayerSurface{ID: 7, Layer: ports.LayerOverlay, Width: 40, Height: 20, Keyboard: 1} | 316 | launcher := ports.LayerSurface{ID: 7, Layer: ports.LayerOverlay, Width: 40, Height: 20, Keyboard: 1} | ||
| 317 | - | bar := ports.LayerSurface{ID: 6, Layer: ports.LayerTop, Anchor: ports.AnchorTop | ports.AnchorLeft | ports.AnchorRight, Height: 5} | 317 | + | bar := ports.LayerSurface{ID: 6, Layer: ports.LayerTop, Anchor: ports.AnchorTop | ports.AnchorLeft | ports.AnchorRight, Height: 5, Keyboard: 2} |
| 318 | client <- ports.LayerChanged{Layers: []ports.LayerSurface{bar, launcher}} | 318 | client <- ports.LayerChanged{Layers: []ports.LayerSurface{bar, launcher}} | ||
| 319 | receive(t, scenes) | 319 | receive(t, scenes) | ||
| 320 | if id := focusOf(); id != 7 { | 320 | if id := focusOf(); id != 7 { | ||