1package core
2
3import "github.com/bnema/neferwl/internal/ports"
4
5// configureTarget is where a placement is shown, as the configure needs it.
6type configureTarget struct {
7 output string
8 area Rect // the output, to tell whether the window is on screen
9 focused bool // activated: focused on the focused output
10 // client is the placement minus borders; imposed reports core sized a
11 // floating window (native floats pick their own size).
12 client Rect
13 imposed bool
14 // captureArea is the viewport of the workspace a capture session renders
15 // off screen, when this window belongs to it (captureConfigure).
16 captureArea Rect
17 // realTiled sizes a visible tiled preview from its real client rect.
18 realTiled bool
19}
20
21// configures owns the last configure sent to each window and decides the
22// next one. Configures are sent only when they change.
23type configures struct {
24 sent map[WindowID]ports.ConfigureWindow
25 seen map[WindowID]bool
26 // answer holds windows whose state request awaits a configure, sent
27 // even unchanged: xdg-shell answers every request, a refused one too.
28 answer map[WindowID]bool
29 // cw is the workspace a capture session renders off screen, if any.
30 cw captureWorkspace
31}
32
33func newConfigures() configures {
34 return configures{sent: map[WindowID]ports.ConfigureWindow{}, seen: map[WindowID]bool{}, answer: map[WindowID]bool{}}
35}
36
37// next returns the configure for p at t and whether it must be sent. The
38// caller records it with mark once sent.
39func (s *configures) next(p Placement, t configureTarget) (ports.ConfigureWindow, bool) {
40 s.seen[p.ID] = true
41 old, ok := s.sent[p.ID]
42 v := build(p, t, old, ok)
43 return v, !ok || v != old || s.answer[p.ID]
44}
45
46// nextWithCapture is next for a window of a workspace a capture session
47// renders off screen. p is its placement on the physical layout; cp is its
48// real placement on its own workspace, laid out in the viewport
49// t.captureArea (nil: not captured, exactly next).
50//
51// A hidden p is sized from cp like a shown window and stays physically
52// invisible and deactivated (Visible false). Any other p (an overview
53// preview) is configured as usual, keeping its real size; only when the
54// display does not show it does it get Captured, so a capture that draws it
55// does not see it suspended. Captured is set when cp is drawn in the
56// viewport.
57func (s *configures) nextWithCapture(p Placement, t configureTarget, cp *Placement) (ports.ConfigureWindow, bool) {
58 if cp == nil {
59 return s.next(p, t)
60 }
61 s.seen[p.ID] = true
62 old, ok := s.sent[p.ID]
63 var v ports.ConfigureWindow
64 if p.Hidden {
65 t.focused = false
66 v = build(*cp, t, old, ok)
67 v.Activated, v.Visible = false, false
68 } else {
69 v = build(p, t, old, ok)
70 }
71 v.Captured = !v.Visible && !cp.Hidden && cp.Rect.Overlaps(t.captureArea)
72 return v, !ok || v != old || s.answer[p.ID]
73}
74
75// build is the configure for p at t given the last one sent, if any. It
76// never sets Captured: only nextWithCapture does, so a window that leaves a
77// capture session drops the flag with its next configure.
78func build(p Placement, t configureTarget, old ports.ConfigureWindow, ok bool) ports.ConfigureWindow {
79 var v ports.ConfigureWindow
80 switch {
81 case p.Hidden:
82 // A hidden window keeps its size, state and output; it is only
83 // deactivated and marked invisible. One mapped hidden is told so
84 // at once, sized by the client.
85 v = old
86 if !ok {
87 v = ports.ConfigureWindow{ID: p.ID, Floating: p.Floating}
88 }
89 v.Activated, v.Visible, v.Output, v.Captured = false, false, t.output, false
90 case p.Preview > 0:
91 // A preview keeps its client's size: only its focus changes.
92 v = old
93 if !ok {
94 v = ports.ConfigureWindow{ID: p.ID, Width: int(float64(p.Rect.W) / p.Preview), Height: int(float64(p.Rect.H) / p.Preview), Floating: p.Floating}
95 if p.Floating {
96 // A native float picks its own size.
97 v.Width, v.Height = 0, 0
98 }
99 }
100 if p.Fullscreen && !v.Fullscreen {
101 // A float made fullscreen off screen: its preview row shows
102 // it fullscreen, sized for the output, never the preview.
103 v.Width, v.Height, v.Fullscreen, v.Floating = t.area.W, t.area.H, true, false
104 }
105 if t.realTiled {
106 v.Width, v.Height = t.client.W, t.client.H
107 }
108 v.Activated, v.Visible, v.Output, v.Captured = t.focused, onScreen(p, t.area), t.output, false
109 default:
110 v = ports.ConfigureWindow{ID: p.ID, Width: t.client.W, Height: t.client.H, Fullscreen: p.Fullscreen, Activated: t.focused, Floating: p.Floating && !p.Fullscreen, Output: t.output, Visible: onScreen(p, t.area)}
111 if v.Floating && !t.imposed {
112 v.Width, v.Height = 0, 0
113 }
114 }
115 return v
116}
117
118// mark records v as sent.
119func (s *configures) mark(v ports.ConfigureWindow) {
120 s.sent[v.ID] = v
121 delete(s.answer, v.ID)
122}
123
124// prune forgets the windows no next call saw since the last prune: they
125// left the layout.
126func (s *configures) prune() {
127 for id := range s.sent {
128 if !s.seen[id] {
129 delete(s.sent, id)
130 }
131 }
132 for id := range s.answer {
133 if !s.seen[id] {
134 delete(s.answer, id)
135 }
136 }
137 clear(s.seen)
138}