1package core
2
3import (
4 "context"
5 "slices"
6
7 "github.com/bnema/neferwl/internal/ports"
8)
9
10// unmanagedState is a mapped override-redirect X11 window: an absolute layout
11// rect chosen by the client, no parent, never keyboard-focused. rect is in
12// global logical coordinates.
13type unmanagedState struct {
14 id WindowID
15 rect Rect
16 noInput bool
17}
18
19// unmanagedEvent applies an unmanaged window event. changed is true when the
20// scene or the pointer target may differ.
21func (c *Core) unmanagedEvent(ctx context.Context, ev ports.ClientEvent) (changed bool, err error) {
22 switch v := ev.(type) {
23 case ports.UnmanagedMapped:
24 r := Rect{X: v.X, Y: v.Y, W: v.Width, H: v.Height}
25 if u := c.unmanaged[v.ID]; u != nil {
26 if u.rect == r && u.noInput == v.NoInput {
27 return false, nil
28 }
29 u.rect, u.noInput = r, v.NoInput
30 } else {
31 c.unmanaged[v.ID] = &unmanagedState{id: v.ID, rect: r, noInput: v.NoInput}
32 c.unmanagedOrder = append(c.unmanagedOrder, v.ID)
33 }
34 case ports.UnmanagedMoved:
35 u := c.unmanaged[v.ID]
36 r := Rect{X: v.X, Y: v.Y, W: v.Width, H: v.Height}
37 if u == nil || u.rect == r {
38 return false, nil
39 }
40 u.rect = r
41 case ports.UnmanagedUnmapped:
42 if c.unmanaged[v.ID] == nil {
43 return false, nil
44 }
45 delete(c.unmanaged, v.ID)
46 c.unmanagedOrder = slices.DeleteFunc(c.unmanagedOrder, func(id WindowID) bool { return id == v.ID })
47 c.windows.drop(v.ID)
48 if err := c.dataDragGone(ctx, v.ID); err != nil {
49 return false, err
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 }
62 }
63 // The window under a still pointer may have changed.
64 return true, c.rehit(ctx)
65}
66
67// unmanagedScreen is the screen showing u: the one containing its origin, or
68// the one the pointer clamp would pick when the origin is in a hole.
69func (c *Core) unmanagedScreen(u *unmanagedState) *screen {
70 x, y := float64(u.rect.X), float64(u.rect.Y)
71 i := c.screenAt(x, y)
72 if i < 0 {
73 cx, cy := c.clampPointer(x, y, x, y)
74 i = c.screenAt(cx, cy)
75 }
76 if i < 0 {
77 return nil
78 }
79 return c.screens[i]
80}
81
82// sceneUnmanaged appends the unmanaged windows shown on sc, bottom to top,
83// drawn as popups over the layers. They are hidden while sc previews.
84func (c *Core) sceneUnmanaged(out []ports.SceneWindow, sc *screen) []ports.SceneWindow {
85 if len(c.unmanagedOrder) == 0 || sc.mon.previewing() {
86 return out
87 }
88 for _, id := range c.unmanagedOrder {
89 u := c.unmanaged[id]
90 if u == nil || c.unmanagedScreen(u) != sc {
91 continue
92 }
93 r := u.rect
94 r.X, r.Y = r.X-sc.x, r.Y-sc.y
95 out = append(out, ports.SceneWindow{ID: id, Rect: r, Popup: true, OverLayers: true})
96 }
97 return out
98}
99
100// unmanagedAt is the topmost unmanaged window taking the pointer at the
101// screen-local point. None while the pointer is locked or the screen previews.
102func (c *Core) unmanagedAt(sc *screen, lx, ly float64) (WindowID, float64, float64) {
103 if len(c.unmanagedOrder) == 0 || c.constraint.Mode == ports.ConstraintLock || sc.mon.previewing() {
104 return 0, 0, 0
105 }
106 for i := len(c.unmanagedOrder) - 1; i >= 0; i-- {
107 u := c.unmanaged[c.unmanagedOrder[i]]
108 if u == nil || u.noInput || c.unmanagedScreen(u) != sc {
109 continue
110 }
111 x, y := lx-float64(u.rect.X-sc.x), ly-float64(u.rect.Y-sc.y)
112 if x >= 0 && x < float64(u.rect.W) && y >= 0 && y < float64(u.rect.H) && c.acceptsInput(u.id, x, y) {
113 return u.id, x, y
114 }
115 }
116 return 0, 0, 0
117}