fix(core): send pointer to floating dialogs over fullscreen windows
- commit
- 30983d57a44a5a1afd2869488e60855d35192a1b
- author
- brice · · 2026-09-25 14:33 UTC
- parent
- 0da7456
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +40 -4
M internal/core/core.go +7 -4
| @@ -519,16 +519,19 @@ func (c *Core) hit(x, y float64) (WindowID, float64, float64) { | |||
| 519 | 519 | } | |
| 520 | 520 | var id WindowID | |
| 521 | 521 | var sx, sy float64 | |
| 522 | + | full := false | |
| 522 | 523 | for _, p := range sc.mon.Layout() { | |
| 523 | 524 | r := c.clientRect(p) | |
| 524 | 525 | if !p.Hidden && r.W > 0 && r.H > 0 && lx >= float64(r.X) && lx < float64(r.X+r.W) && ly >= float64(r.Y) && ly < float64(r.Y+r.H) { | |
| 525 | - | // Floating windows come last in the layout and are on top. | |
| 526 | + | // Floating windows come last in the layout and are on top, | |
| 527 | + | // even of a fullscreen window; nothing else is. | |
| 528 | + | if full && !p.Floating { | |
| 529 | + | continue | |
| 530 | + | } | |
| 526 | 531 | if id == 0 || p.Fullscreen || p.Floating { | |
| 527 | 532 | id, sx, sy = p.ID, lx-float64(r.X), ly-float64(r.Y) | |
| 528 | 533 | } | |
| 529 | - | if p.Fullscreen { | |
| 530 | - | break | |
| 531 | - | } | |
| 534 | + | full = full || p.Fullscreen | |
| 532 | 535 | } | |
| 533 | 536 | } | |
| 534 | 537 | return id, sx, sy | |
| @@ -519,16 +519,19 @@ func (c *Core) hit(x, y float64) (WindowID, float64, float64) { | |||||
| 519 | } | 519 | } | ||
| 520 | var id WindowID | 520 | var id WindowID | ||
| 521 | var sx, sy float64 | 521 | var sx, sy float64 | ||
| 522 | + | full := false | |||
| 522 | for _, p := range sc.mon.Layout() { | 523 | for _, p := range sc.mon.Layout() { | ||
| 523 | r := c.clientRect(p) | 524 | r := c.clientRect(p) | ||
| 524 | if !p.Hidden && r.W > 0 && r.H > 0 && lx >= float64(r.X) && lx < float64(r.X+r.W) && ly >= float64(r.Y) && ly < float64(r.Y+r.H) { | 525 | if !p.Hidden && r.W > 0 && r.H > 0 && lx >= float64(r.X) && lx < float64(r.X+r.W) && ly >= float64(r.Y) && ly < float64(r.Y+r.H) { | ||
| 525 | - | // Floating windows come last in the layout and are on top. | 526 | + | // Floating windows come last in the layout and are on top, |
| 527 | + | // even of a fullscreen window; nothing else is. | |||
| 528 | + | if full && !p.Floating { | |||
| 529 | + | continue | |||
| 530 | + | } | |||
| 526 | if id == 0 || p.Fullscreen || p.Floating { | 531 | if id == 0 || p.Fullscreen || p.Floating { | ||
| 527 | id, sx, sy = p.ID, lx-float64(r.X), ly-float64(r.Y) | 532 | id, sx, sy = p.ID, lx-float64(r.X), ly-float64(r.Y) | ||
| 528 | } | 533 | } | ||
| 529 | - | if p.Fullscreen { | 534 | + | full = full || p.Fullscreen |
| 530 | - | break | |||
| 531 | - | } | |||
| 532 | } | 535 | } | ||
| 533 | } | 536 | } | ||
| 534 | return id, sx, sy | 537 | return id, sx, sy | ||
M internal/core/core_test.go +33 -0
| @@ -689,3 +689,36 @@ func TestPopupChainDismissOrder(t *testing.T) { | |||
| 689 | 689 | t.Fatalf("close order %v, want [3 2]", closed) | |
| 690 | 690 | } | |
| 691 | 691 | } | |
| 692 | + | ||
| 693 | + | // A dialog over a fullscreen window gets the pointer. | |
| 694 | + | func TestFloatOverFullscreenHit(t *testing.T) { | |
| 695 | + | cfg := config.Defaults() | |
| 696 | + | cfg.Border.Width = 0 | |
| 697 | + | client := make(chan ports.ClientEvent, 8) | |
| 698 | + | input := make(chan ports.InputEvent, 8) | |
| 699 | + | output := make(chan ports.OutputEvent, 8) | |
| 700 | + | commands := make(chan ports.ClientCommand, 64) | |
| 701 | + | scenes := make(chan []ports.Scene, 1) | |
| 702 | + | c, err := core.New(cfg, core.Channels{Client: client, Input: input, Output: output, Commands: commands, Scenes: scenes}) | |
| 703 | + | if err != nil { | |
| 704 | + | t.Fatal(err) | |
| 705 | + | } | |
| 706 | + | ctx, cancel := context.WithCancel(context.Background()) | |
| 707 | + | defer cancel() | |
| 708 | + | go c.Run(ctx) | |
| 709 | + | output <- ports.OutputAdded{Info: ports.OutputInfo{Name: "OUT-1", Width: 100, Height: 80}} | |
| 710 | + | scene(t, scenes) | |
| 711 | + | client <- ports.WindowMapped{ID: 1} | |
| 712 | + | scene(t, scenes) | |
| 713 | + | client <- ports.WindowFullscreenRequest{ID: 1, Fullscreen: true} | |
| 714 | + | scene(t, scenes) | |
| 715 | + | client <- ports.WindowMapped{ID: 2, Floating: true, Width: 20, Height: 10} | |
| 716 | + | scene(t, scenes) | |
| 717 | + | for len(commands) > 0 { | |
| 718 | + | <-commands | |
| 719 | + | } | |
| 720 | + | input <- ports.PointerMotion{X: 50, Y: 40} | |
| 721 | + | if v, ok := command(t, commands).(ports.PointerFocus); !ok || v.ID != 2 { | |
| 722 | + | t.Fatalf("pointer went to %v, want the dialog", v) | |
| 723 | + | } | |
| 724 | + | } | |
| @@ -689,3 +689,36 @@ func TestPopupChainDismissOrder(t *testing.T) { | |||||
| 689 | t.Fatalf("close order %v, want [3 2]", closed) | 689 | t.Fatalf("close order %v, want [3 2]", closed) | ||
| 690 | } | 690 | } | ||
| 691 | } | 691 | } | ||
| 692 | + | ||||
| 693 | + | // A dialog over a fullscreen window gets the pointer. | |||
| 694 | + | func TestFloatOverFullscreenHit(t *testing.T) { | |||
| 695 | + | cfg := config.Defaults() | |||
| 696 | + | cfg.Border.Width = 0 | |||
| 697 | + | client := make(chan ports.ClientEvent, 8) | |||
| 698 | + | input := make(chan ports.InputEvent, 8) | |||
| 699 | + | output := make(chan ports.OutputEvent, 8) | |||
| 700 | + | commands := make(chan ports.ClientCommand, 64) | |||
| 701 | + | scenes := make(chan []ports.Scene, 1) | |||
| 702 | + | c, err := core.New(cfg, core.Channels{Client: client, Input: input, Output: output, Commands: commands, Scenes: scenes}) | |||
| 703 | + | if err != nil { | |||
| 704 | + | t.Fatal(err) | |||
| 705 | + | } | |||
| 706 | + | ctx, cancel := context.WithCancel(context.Background()) | |||
| 707 | + | defer cancel() | |||
| 708 | + | go c.Run(ctx) | |||
| 709 | + | output <- ports.OutputAdded{Info: ports.OutputInfo{Name: "OUT-1", Width: 100, Height: 80}} | |||
| 710 | + | scene(t, scenes) | |||
| 711 | + | client <- ports.WindowMapped{ID: 1} | |||
| 712 | + | scene(t, scenes) | |||
| 713 | + | client <- ports.WindowFullscreenRequest{ID: 1, Fullscreen: true} | |||
| 714 | + | scene(t, scenes) | |||
| 715 | + | client <- ports.WindowMapped{ID: 2, Floating: true, Width: 20, Height: 10} | |||
| 716 | + | scene(t, scenes) | |||
| 717 | + | for len(commands) > 0 { | |||
| 718 | + | <-commands | |||
| 719 | + | } | |||
| 720 | + | input <- ports.PointerMotion{X: 50, Y: 40} | |||
| 721 | + | if v, ok := command(t, commands).(ports.PointerFocus); !ok || v.ID != 2 { | |||
| 722 | + | t.Fatalf("pointer went to %v, want the dialog", v) | |||
| 723 | + | } | |||
| 724 | + | } | |||