fix(core): validate pointer grabs through the grab interface
- commit
- 080065e5f02e0ef847addb369820596c348aac48
- author
- brice · · 2026-10-21 05:42 UTC
- committer
- brice · · 2026-10-21 07:11 UTC
- parent
- 2916cea
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +16 -2
M internal/core/client.go +1 -1
| @@ -136,7 +136,7 @@ func (c *Core) unmapClient(ctx context.Context, v ports.WindowUnmapped) error { | |||
| 136 | 136 | return err | |
| 137 | 137 | } | |
| 138 | 138 | c.unmapWindow(v) | |
| 139 | - | if g, ok := c.pgrab.(*borderResize); ok && !g.valid() { | |
| 139 | + | if c.pgrab != nil && !c.pgrab.valid() { | |
| 140 | 140 | if err := c.abortPointerGrab(ctx); err != nil { | |
| 141 | 141 | return err | |
| 142 | 142 | } | |
| @@ -136,7 +136,7 @@ func (c *Core) unmapClient(ctx context.Context, v ports.WindowUnmapped) error { | |||||
| 136 | return err | 136 | return err | ||
| 137 | } | 137 | } | ||
| 138 | c.unmapWindow(v) | 138 | c.unmapWindow(v) | ||
| 139 | - | if g, ok := c.pgrab.(*borderResize); ok && !g.valid() { | 139 | + | if c.pgrab != nil && !c.pgrab.valid() { |
| 140 | if err := c.abortPointerGrab(ctx); err != nil { | 140 | if err := c.abortPointerGrab(ctx); err != nil { | ||
| 141 | return err | 141 | return err | ||
| 142 | } | 142 | } | ||
M internal/core/core.go +1 -1
| @@ -637,7 +637,7 @@ func (c *Core) publishFrame(ctx context.Context, only *screen) error { | |||
| 637 | 637 | sc.mon.Current().noteFocus() | |
| 638 | 638 | } | |
| 639 | 639 | } | |
| 640 | - | if g, ok := c.pgrab.(*borderResize); ok && !g.valid() { | |
| 640 | + | if c.pgrab != nil && !c.pgrab.valid() { | |
| 641 | 641 | if err := c.abortPointerGrab(ctx); err != nil { | |
| 642 | 642 | return err | |
| 643 | 643 | } | |
| @@ -637,7 +637,7 @@ func (c *Core) publishFrame(ctx context.Context, only *screen) error { | |||||
| 637 | sc.mon.Current().noteFocus() | 637 | sc.mon.Current().noteFocus() | ||
| 638 | } | 638 | } | ||
| 639 | } | 639 | } | ||
| 640 | - | if g, ok := c.pgrab.(*borderResize); ok && !g.valid() { | 640 | + | if c.pgrab != nil && !c.pgrab.valid() { |
| 641 | if err := c.abortPointerGrab(ctx); err != nil { | 641 | if err := c.abortPointerGrab(ctx); err != nil { | ||
| 642 | return err | 642 | return err | ||
| 643 | } | 643 | } | ||
M internal/core/pointergrab.go +2 -0
| @@ -12,6 +12,8 @@ import ( | |||
| 12 | 12 | // (border_resize.go); planned: column move within a workspace, and edge-hold | |
| 13 | 13 | // transfer of a dragged column to the next workspace or the adjacent monitor. | |
| 14 | 14 | type pointerGrab interface { | |
| 15 | + | // valid reports whether the grab still applies to the current layout; the core aborts it otherwise. | |
| 16 | + | valid() bool | |
| 15 | 17 | button() uint32 | |
| 16 | 18 | motion(ctx context.Context, at time.Duration) error | |
| 17 | 19 | release(ctx context.Context) error | |
| @@ -12,6 +12,8 @@ import ( | |||||
| 12 | // (border_resize.go); planned: column move within a workspace, and edge-hold | 12 | // (border_resize.go); planned: column move within a workspace, and edge-hold | ||
| 13 | // transfer of a dragged column to the next workspace or the adjacent monitor. | 13 | // transfer of a dragged column to the next workspace or the adjacent monitor. | ||
| 14 | type pointerGrab interface { | 14 | type pointerGrab interface { | ||
| 15 | + | // valid reports whether the grab still applies to the current layout; the core aborts it otherwise. | |||
| 16 | + | valid() bool | |||
| 15 | button() uint32 | 17 | button() uint32 | ||
| 16 | motion(ctx context.Context, at time.Duration) error | 18 | motion(ctx context.Context, at time.Duration) error | ||
| 17 | release(ctx context.Context) error | 19 | release(ctx context.Context) error | ||
M internal/core/pointergrab_test.go +12 -0
| @@ -250,3 +250,15 @@ func TestBorderGrabSecurityReset(t *testing.T) { | |||
| 250 | 250 | t.Fatal("security reset sent commands") | |
| 251 | 251 | } | |
| 252 | 252 | } | |
| 253 | + | ||
| 254 | + | func TestPointerGrabValidityContract(t *testing.T) { | |
| 255 | + | c, _ := borderCore(t, false) | |
| 256 | + | grabButton(t, c, btnRight, true, time.Second) | |
| 257 | + | if !c.pgrab.valid() { | |
| 258 | + | t.Fatal("new grab is invalid") | |
| 259 | + | } | |
| 260 | + | c.screens = nil | |
| 261 | + | if c.pgrab.valid() { | |
| 262 | + | t.Fatal("grab survived output removal") | |
| 263 | + | } | |
| 264 | + | } | |
| @@ -250,3 +250,15 @@ func TestBorderGrabSecurityReset(t *testing.T) { | |||||
| 250 | t.Fatal("security reset sent commands") | 250 | t.Fatal("security reset sent commands") | ||
| 251 | } | 251 | } | ||
| 252 | } | 252 | } | ||
| 253 | + | ||||
| 254 | + | func TestPointerGrabValidityContract(t *testing.T) { | |||
| 255 | + | c, _ := borderCore(t, false) | |||
| 256 | + | grabButton(t, c, btnRight, true, time.Second) | |||
| 257 | + | if !c.pgrab.valid() { | |||
| 258 | + | t.Fatal("new grab is invalid") | |||
| 259 | + | } | |||
| 260 | + | c.screens = nil | |||
| 261 | + | if c.pgrab.valid() { | |||
| 262 | + | t.Fatal("grab survived output removal") | |||
| 263 | + | } | |||
| 264 | + | } | |||