1package core
2
3import (
4 "context"
5
6 "github.com/bnema/neferwl/internal/ports"
7)
8
9// clientEvent applies one client event. publish asks for a new scene;
10// stop ends Run (a command could not be delivered).
11func (c *Core) clientEvent(ctx context.Context, ev ports.ClientEvent) (publish, stop bool) {
12 switch v := ev.(type) {
13 case ports.SessionLockChanged:
14 if !c.applyLockChanged(v) {
15 return false, false
16 }
17 case ports.CaptureSessionOpen:
18 c.captureOpen(v)
19 case ports.CaptureSessionClose:
20 c.captureClose(v.ID)
21 case ports.CaptureFrameTaken:
22 // Nothing new to show (the target flashes already): no scene.
23 if !c.captureFrame(v) {
24 return false, false
25 }
26 case ports.CaptureExclusionBegin:
27 c.captureExclusionBegin(v)
28 case ports.CaptureExclusionLayer:
29 c.captureExclusionLayer(v)
30 case ports.CaptureExclusionEnd:
31 c.captureExclusionEnd(v.Session)
32 case ports.LayerChanged:
33 c.layerChanged = true
34 c.setLayers(v.Layers)
35 case ports.InputRegionChanged:
36 c.windows.setRegion(v)
37 case ports.WindowMapped:
38 c.mapWindow(v)
39 case ports.WindowResized:
40 c.resizeFloating(v)
41 case ports.PopupRequest:
42 if err := c.placePopup(ctx, v); err != nil {
43 return false, true
44 }
45 case ports.PopupMapped:
46 if p := c.popups[v.ID]; p != nil {
47 p.mapped = true
48 }
49 case ports.ShortcutsInhibit:
50 c.windows.setInhibitShortcuts(v.Window, v.Active)
51 if err := c.updateInhibit(ctx); err != nil {
52 return false, true
53 }
54 return false, false
55 case ports.OutputPower:
56 if i := c.screenIndex(v.Output); i >= 0 && c.screens[i].off == v.On {
57 c.screens[i].off = !v.On
58 }
59 case ports.IdleInhibit:
60 c.windows.setIdleInhibit(v.Window, v.Active)
61 c.publishState()
62 return false, false
63 case ports.WindowAppID:
64 c.windows.setAppID(v.ID, v.AppID)
65 case ports.WindowParent:
66 if _, w := c.screenOf(v.ID); w != nil {
67 w.SetDialogParent(v.ID, v.Parent)
68 }
69 case ports.WindowModal:
70 if _, w := c.screenOf(v.ID); w != nil {
71 w.SetModal(v.ID, v.Modal)
72 }
73 case ports.UnmanagedMapped, ports.UnmanagedMoved, ports.UnmanagedUnmapped:
74 changed, err := c.unmanagedEvent(ctx, v)
75 if err != nil {
76 return false, true
77 }
78 if !changed {
79 return false, false
80 }
81 case ports.WindowUnmapped:
82 if c.unmapClient(ctx, v) != nil {
83 return false, true
84 }
85 case ports.PointerConstrained:
86 c.constrained = v
87 case ports.PointerWarp:
88 if c.warpPointer(ctx, v) != nil {
89 return false, true
90 }
91 case ports.WindowMoveRequest:
92 if c.clientDrag(ctx, v) != nil {
93 return false, true
94 }
95 case ports.DataDragStarted:
96 if c.startDataDrag(ctx, v) != nil {
97 return false, true
98 }
99 return false, false
100 case ports.DataDragIcon:
101 if !c.setDataDragIcon(v) {
102 return false, false
103 }
104 case ports.DataDragEnded:
105 if c.dataDrag == nil {
106 return false, false
107 }
108 c.abortDataDrag()
109 if c.finishDataDrag(ctx) != nil {
110 return false, true
111 }
112 return false, false
113 case ports.WindowFullscreenRequest:
114 if c.fullscreenRequest(ctx, v) != nil {
115 return false, true
116 }
117 case ports.WorkspaceActivate:
118 if c.activateWorkspaces(ctx, v) != nil {
119 return false, true
120 }
121 case ports.WindowActivate:
122 if c.activate(ctx, v.ID) != nil {
123 return false, true
124 }
125 }
126 return true, false
127}
128
129// unmapClient drops a window or popup the client unmapped.
130func (c *Core) unmapClient(ctx context.Context, v ports.WindowUnmapped) error {
131 if c.popups[v.ID] != nil {
132 if err := c.dropPopup(ctx, v.ID); err != nil {
133 return err
134 }
135 }
136 if err := c.closePopupsOf(ctx, v.ID); err != nil {
137 return err
138 }
139 c.windows.drop(v.ID)
140 if c.drag != nil && c.drag.id == v.ID {
141 c.abortDrag()
142 }
143 if err := c.dataDragGone(ctx, v.ID); err != nil {
144 return err
145 }
146 c.unmapWindow(v)
147 if c.pgrab != nil && !c.pgrab.valid() {
148 if err := c.abortPointerGrab(ctx); err != nil {
149 return err
150 }
151 }
152 c.releaseSlots()
153 if c.pointer == v.ID {
154 c.pointer = 0
155 if err := c.pointerFocus(ctx, ports.PointerFocus{}); err != nil {
156 return err
157 }
158 }
159 return nil
160}
161
162// fullscreenRequest answers xdg_toplevel set/unset_fullscreen, from the
163// window itself or a taskbar (External).
164func (c *Core) fullscreenRequest(ctx context.Context, v ports.WindowFullscreenRequest) error {
165 c.configures.answer[v.ID] = true
166 if v.Fullscreen && !v.External && c.now().Sub(c.windows.lookup(v.ID).mappedAt) < fullscreenGrace {
167 return nil
168 }
169 // A taskbar request is a user action on that window, as an
170 // activation: it comes on screen with the focus, leaving
171 // another window's fullscreen.
172 if v.External && v.Fullscreen {
173 if err := c.activate(ctx, v.ID); err != nil {
174 return err
175 }
176 }
177 if s, _ := c.screenOf(v.ID); s != nil {
178 s.mon.SetFullscreen(v.ID, v.Fullscreen)
179 if w := s.mon.Current(); v.Fullscreen && w.fullscreen == v.ID {
180 // A taskbar or late fullscreen request that took
181 // effect: the window does not fade in, it keeps the
182 // direct scanout path. One that was ignored (the
183 // grace after the map) leaves the entrance running.
184 delete(s.rects, v.ID)
185 }
186 }
187 return nil
188}
189
190// activateWorkspaces shows the workspaces of v by stable ID, in order.
191func (c *Core) activateWorkspaces(ctx context.Context, v ports.WorkspaceActivate) error {
192 before := c.cur().mon.Current()
193 for _, id := range v.IDs {
194 for i, sc := range c.screens {
195 if sc.name() == "" {
196 continue
197 }
198 for w := range sc.mon.all() {
199 if w.ID == id {
200 c.focusScreen = i
201 sc.mon.show(w)
202 break
203 }
204 }
205 }
206 }
207 return c.workspaceVisible(ctx, c.cur().mon.Current() != before)
208}