detached 30983d5

fix(core): send pointer to floating dialogs over fullscreen windows

commit
30983d57a44a5a1afd2869488e60855d35192a1b
author
brice · · 2026-09-25 14:33 UTC
parent
0da7456
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · 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) {
519519 }
520520 var id WindowID
521521 var sx, sy float64
522+ full := false
522523 for _, p := range sc.mon.Layout() {
523524 r := c.clientRect(p)
524525 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+ }
526531 if id == 0 || p.Fullscreen || p.Floating {
527532 id, sx, sy = p.ID, lx-float64(r.X), ly-float64(r.Y)
528533 }
529- if p.Fullscreen {
530- break
531- }
534+ full = full || p.Fullscreen
532535 }
533536 }
534537 return id, sx, sy
@@ -519,16 +519,19 @@ func (c *Core) hit(x, y float64) (WindowID, float64, float64) {
519 }519 }
520 var id WindowID520 var id WindowID
521 var sx, sy float64521 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, sy537 return id, sx, sy
M internal/core/core_test.go +33 -0
@@ -689,3 +689,36 @@ func TestPopupChainDismissOrder(t *testing.T) {
689689 t.Fatalf("close order %v, want [3 2]", closed)
690690 }
691691 }
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+}

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 23

Tags 10

↑↓ moveenter openesc close