fix(core): clear pointer and grab when an unmanaged window unmaps
- commit
- 8ab1d07e6792b323831afb58b582827427c84aca
- author
- brice · · 2026-10-22 15:27 UTC
- committer
- brice · · 2026-10-22 17:54 UTC
- parent
- b3f6b3a
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +32 -0
M internal/core/unmanaged.go +11 -0
| @@ -48,6 +48,17 @@ func (c *Core) unmanagedEvent(ctx context.Context, ev ports.ClientEvent) (change | |||
| 48 | 48 | if err := c.dataDragGone(ctx, v.ID); err != nil { | |
| 49 | 49 | return false, err | |
| 50 | 50 | } | |
| 51 | + | // rehit does nothing while a button grab or drag is held: do not | |
| 52 | + | // leave the pointer and grab on the dead window. | |
| 53 | + | if c.grab == v.ID { | |
| 54 | + | c.grab = 0 | |
| 55 | + | } | |
| 56 | + | if c.pointer == v.ID { | |
| 57 | + | c.pointer = 0 | |
| 58 | + | if err := c.pointerFocus(ctx, ports.PointerFocus{}); err != nil { | |
| 59 | + | return false, err | |
| 60 | + | } | |
| 61 | + | } | |
| 51 | 62 | } | |
| 52 | 63 | // The window under a still pointer may have changed. | |
| 53 | 64 | return true, c.rehit(ctx) | |
| @@ -48,6 +48,17 @@ func (c *Core) unmanagedEvent(ctx context.Context, ev ports.ClientEvent) (change | |||||
| 48 | if err := c.dataDragGone(ctx, v.ID); err != nil { | 48 | if err := c.dataDragGone(ctx, v.ID); err != nil { | ||
| 49 | return false, err | 49 | return false, err | ||
| 50 | } | 50 | } | ||
| 51 | + | // rehit does nothing while a button grab or drag is held: do not | |||
| 52 | + | // leave the pointer and grab on the dead window. | |||
| 53 | + | if c.grab == v.ID { | |||
| 54 | + | c.grab = 0 | |||
| 55 | + | } | |||
| 56 | + | if c.pointer == v.ID { | |||
| 57 | + | c.pointer = 0 | |||
| 58 | + | if err := c.pointerFocus(ctx, ports.PointerFocus{}); err != nil { | |||
| 59 | + | return false, err | |||
| 60 | + | } | |||
| 61 | + | } | |||
| 51 | } | 62 | } | ||
| 52 | // The window under a still pointer may have changed. | 63 | // The window under a still pointer may have changed. | ||
| 53 | return true, c.rehit(ctx) | 64 | return true, c.rehit(ctx) | ||
M internal/core/unmanaged_test.go +21 -0
| @@ -122,3 +122,24 @@ func TestUnmanagedHitAllocations(t *testing.T) { | |||
| 122 | 122 | t.Fatalf("hit allocs = %v", n) | |
| 123 | 123 | } | |
| 124 | 124 | } | |
| 125 | + | ||
| 126 | + | func TestUnmanagedUnmapDuringGrabClearsPointer(t *testing.T) { | |
| 127 | + | c := publishRig(t, 1) | |
| 128 | + | mapUnmanaged(t, c, 100, 10, 10, 50, 40, false) | |
| 129 | + | cmds := make(chan ports.ClientCommand, 64) | |
| 130 | + | c.ch.Commands = cmds | |
| 131 | + | c.pointer, c.grab = 100, 100 | |
| 132 | + | c.clientEvent(context.Background(), ports.UnmanagedUnmapped{ID: 100}) | |
| 133 | + | if c.pointer == 100 || c.grab != 0 { | |
| 134 | + | t.Fatalf("pointer %d grab %d left on the dead window", c.pointer, c.grab) | |
| 135 | + | } | |
| 136 | + | var focus bool | |
| 137 | + | for len(cmds) > 0 { | |
| 138 | + | if f, ok := (<-cmds).(ports.PointerFocus); ok && f == (ports.PointerFocus{}) { | |
| 139 | + | focus = true | |
| 140 | + | } | |
| 141 | + | } | |
| 142 | + | if !focus { | |
| 143 | + | t.Fatal("pointer focus not reset") | |
| 144 | + | } | |
| 145 | + | } | |
| @@ -122,3 +122,24 @@ func TestUnmanagedHitAllocations(t *testing.T) { | |||||
| 122 | t.Fatalf("hit allocs = %v", n) | 122 | t.Fatalf("hit allocs = %v", n) | ||
| 123 | } | 123 | } | ||
| 124 | } | 124 | } | ||
| 125 | + | ||||
| 126 | + | func TestUnmanagedUnmapDuringGrabClearsPointer(t *testing.T) { | |||
| 127 | + | c := publishRig(t, 1) | |||
| 128 | + | mapUnmanaged(t, c, 100, 10, 10, 50, 40, false) | |||
| 129 | + | cmds := make(chan ports.ClientCommand, 64) | |||
| 130 | + | c.ch.Commands = cmds | |||
| 131 | + | c.pointer, c.grab = 100, 100 | |||
| 132 | + | c.clientEvent(context.Background(), ports.UnmanagedUnmapped{ID: 100}) | |||
| 133 | + | if c.pointer == 100 || c.grab != 0 { | |||
| 134 | + | t.Fatalf("pointer %d grab %d left on the dead window", c.pointer, c.grab) | |||
| 135 | + | } | |||
| 136 | + | var focus bool | |||
| 137 | + | for len(cmds) > 0 { | |||
| 138 | + | if f, ok := (<-cmds).(ports.PointerFocus); ok && f == (ports.PointerFocus{}) { | |||
| 139 | + | focus = true | |||
| 140 | + | } | |||
| 141 | + | } | |||
| 142 | + | if !focus { | |||
| 143 | + | t.Fatal("pointer focus not reset") | |||
| 144 | + | } | |||
| 145 | + | } | |||