1package ports
2
3import "time"
4
5// Capture sessions, exclusion and workspace sources.
6//
7// Core owns every decision; wayland relays requests and what core decided.
8// Registered sessions are the ext-image-copy-capture sessions
9// (CaptureSessionOpen). One of them at most has an exclusion
10// (neferwl_capture_exclusion_v1): the layer surfaces its HUD attached stay
11// visible over a fullscreen window and are left out of that session's frames.
12//
13// Every capture is shown on screen by the compositor, never by the client
14// (CaptureIndicator): a border on the target of a recording session for as
15// long as it lives, and on the target of every captured frame for
16// CaptureFlash after it. No capture image ever holds an indicator.
17
18const (
19 // MaxExclusionLayers bounds the layer surfaces attached to an exclusion.
20 MaxExclusionLayers = 4
21 // MaxCaptureExcluded bounds SceneCapture.Excluded: attached layers and
22 // their popup descendants.
23 MaxCaptureExcluded = 128
24 // CaptureBorderWidth and CaptureBorderColor are the indicator border, in
25 // logical pixels and #rrggbb. CapturePillSize, CapturePillInset and
26 // CapturePillRadius are the marker of a target that is not on screen
27 // (a workspace rendered for capture only): a rounded square in the
28 // top-right corner of its output, in logical pixels.
29 CaptureBorderWidth = 2
30 CaptureBorderColor = "#ff3b30"
31 CapturePillSize = 12
32 CapturePillInset = 8
33 CapturePillRadius = 3
34 // CaptureFlash is how long the indicator of a captured frame stays on
35 // screen after that frame.
36 CaptureFlash = time.Second
37)
38
39// CaptureReason says why a registered session ends. The zero value is none.
40type CaptureReason string
41
42const (
43 CaptureReasonNone CaptureReason = ""
44 // Every reason ends the session: core forgets it and wayland stops it.
45 CaptureReasonOutputGone CaptureReason = "output-gone"
46 CaptureReasonWorkspaceGone CaptureReason = "workspace-gone"
47 CaptureReasonInvalidRegion CaptureReason = "invalid-region" // nothing of the region is left on the output
48 // TooManyExcluded: the attached layers and their popups exceed
49 // MaxCaptureExcluded; a capture could not leave them all out (fail closed).
50 CaptureReasonTooManyExcluded CaptureReason = "too-many-excluded"
51)
52
53// Terminal reports whether a state with this reason ends the session.
54func (r CaptureReason) Terminal() bool { return r != CaptureReasonNone }
55
56// CaptureSessionOpen carries wayland → core a new ext-image-copy-capture
57// session. Its target is an output (Workspace 0), whole when Region is zero,
58// else the part of it in Region (output-local logical pixels, clipped), or a
59// workspace (ID of WorkspaceInfo.ID): its frame, on whichever output owns it
60// now. ID is chosen by wayland and unique for the process lifetime.
61type CaptureSessionOpen struct {
62 ID uint64
63 Output string
64 Workspace uint64
65 Region Rect
66}
67
68func (CaptureSessionOpen) clientEvent() {}
69
70// CaptureSessionClose carries wayland → core the end of a session, for any
71// reason. Unknown IDs are ignored.
72type CaptureSessionClose struct{ ID uint64 }
73
74func (CaptureSessionClose) clientEvent() {}
75
76// CaptureFrameTaken carries wayland → core a capture accepted for rendering,
77// from any protocol: a wlr-screencopy frame (Session 0) or a frame of an
78// ext-image-copy-capture session. Output and Region, or Workspace, are the
79// target as the client asked for it: an output (Region zero is the whole
80// output, else output-local logical pixels, clipped by core) or a workspace
81// (Workspace is its ID, Output and Region are empty). Core shows the
82// indicator on that target for CaptureFlash after it, and while Session
83// lives when it is not 0.
84type CaptureFrameTaken struct {
85 Session uint64
86 Output string
87 Workspace uint64
88 Region Rect
89}
90
91func (CaptureFrameTaken) clientEvent() {}
92
93// CaptureExclusionBegin carries wayland → core the exclusion of a session.
94// Wayland has already refused it when another one is live.
95type CaptureExclusionBegin struct{ Session uint64 }
96
97func (CaptureExclusionBegin) clientEvent() {}
98
99// CaptureExclusionLayer carries wayland → core a layer surface the session's
100// authorised HUD attached (or detached, when it was destroyed). Layer is the
101// LayerSurface.ID, known before the surface maps. Wayland has already checked
102// the layer is top or overlay and that it never takes the keyboard
103// exclusively; at most MaxExclusionLayers are attached.
104type CaptureExclusionLayer struct {
105 Session uint64
106 Layer WindowID
107 Attached bool
108}
109
110func (CaptureExclusionLayer) clientEvent() {}
111
112// CaptureExclusionEnd carries wayland → core the end of an exclusion whose
113// session lives on (its object was destroyed). A session that closes ends
114// its exclusion with it. The layers of an ended exclusion that a screen still
115// lists stay excluded and fenced (CaptureSessionState.Exclusion, Revision)
116// until none is listed, so no frame of the session shows the HUD afterwards.
117type CaptureExclusionEnd struct{ Session uint64 }
118
119func (CaptureExclusionEnd) clientEvent() {}
120
121// CaptureSessionState carries core → wayland what core decided about a
122// registered session, on every change of it. Output is the output that owns
123// the target now, Rect the clipped target in output-local logical pixels (for
124// a workspace, its frame) and Workspace the workspace ID, 0 for an output.
125// Hidden means the workspace is not drawn as itself on screen: it is rendered
126// for capture only (Scene.CaptureScene) and frames must ask for it
127// (CaptureRequest.Workspace). Active means frames are served; a hidden
128// workspace is rendered for one session at a time in the compositor (its
129// Workspace): another hidden one is not Active until that one is gone. A
130// non-empty Reason ends the session (wayland stops it).
131//
132// Exclusion says core knows an exclusion of this session. Revision is its
133// revision fence, from 1, and Layers the attached layer surfaces core knows
134// (at most MaxExclusionLayers); both are zero without an exclusion. Core
135// bumps the revision whenever what an excluded frame must leave out changes:
136// the start, an attached or detached layer, an excluded popup, the target. The
137// scene carrying that change has the same SceneCapture.Revision. Wayland
138// confirms an attach to the client only once a state lists the layer, serves
139// the session's frames with CaptureRequest.Exclude and stamps
140// CaptureRevision with the last state's Revision; a renderer refuses the
141// request unless its scene's Capture.Revision is at least that, so a scene
142// published before the change is never used and no stale exclusion list can
143// leak a HUD.
144type CaptureSessionState struct {
145 ID uint64
146 Output string
147 Rect Rect
148 Workspace uint64
149 Hidden bool
150 Active bool
151 Reason CaptureReason
152 Exclusion bool
153 Revision uint64
154 Layers []WindowID
155}
156
157func (CaptureSessionState) clientCommand() {}
158
159// SceneCapture carries core → renderer the capture state of one output's
160// scene (Scene.Capture; nil when there is none, which is the whole cost of
161// the feature while no session exists).
162//
163// Session is the session that owns the exclusion, 0 without one; Excluded
164// lists its attached layer surfaces and their popup descendants (at most
165// MaxCaptureExcluded), which that session's frames leave out, and Revision is
166// the fence of CaptureSessionState. Workspace is the ID of the hidden
167// workspace CaptureScene draws, 0 when there is none on this output. Shown is
168// the ID of the workspace this scene draws as itself (0 in the overview): a
169// request for a workspace is served only when it is Shown here, or is the
170// hidden Workspace; else it fails, whatever wayland last believed.
171type SceneCapture struct {
172 Shown uint64
173 Session uint64
174 Excluded []WindowID
175 Revision uint64
176 Workspace uint64
177}
178
179// CaptureIndicator carries core → renderer one mark of a capture on an
180// output's scene (Scene.CaptureIndicators; nil while nothing is captured,
181// which is the whole cost of the feature). The renderer draws it last, over
182// everything, in logical pixels; it is only on the scene of the physical
183// output and in no capture image (the capture pipeline draws captures
184// without it).
185//
186// Without Pill the indicator is the border of a target on screen: Rect is
187// the mark, output-local and clipped, and the border is drawn inside it, at
188// most half its smaller side wide (CaptureBorderOf). Core never lists a mark
189// thinner than MinCaptureMark unless the output itself is: a border of a
190// 1-pixel target would have nothing to draw. With Pill it marks a target
191// that is not on screen: Rect is the pill square, a rounded one.
192type CaptureIndicator struct {
193 Rect Rect
194 Pill bool
195}
196
197// MinCaptureMark is the smallest side of a border mark: the border on both
198// sides and one pixel between them.
199const MinCaptureMark = 2*CaptureBorderWidth + 1
200
201// CaptureBorderOf is the width of the border drawn inside a w×h mark: the
202// indicator width, at most half of each side. Zero means nothing is drawn
203// for a proper border; the renderer then fills the whole mark, and the fence
204// refuses such a mark (the thin mark of a capture it cannot vouch for).
205func CaptureBorderOf(w, h int) int { return min(CaptureBorderWidth, w/2, h/2) }
206
207// Drawable reports whether the indicator draws a visible mark, wherever the
208// output is: a non-empty rectangle whose border, for a border mark, has a
209// width. A pill must also lie in the output (DrawableIn).
210func (m CaptureIndicator) Drawable() bool {
211 if m.Rect.W <= 0 || m.Rect.H <= 0 {
212 return false
213 }
214 return m.Pill || CaptureBorderOf(m.Rect.W, m.Rect.H) > 0
215}
216
217// DrawableIn is Drawable on an output of w×h logical pixels: a pill must lie
218// fully inside it (an empty or partly outside pill is cut by the renderer and
219// may show nothing).
220func (m CaptureIndicator) DrawableIn(w, h int) bool {
221 if !m.Drawable() {
222 return false
223 }
224 if !m.Pill {
225 return true
226 }
227 r := m.Rect
228 return r.X >= 0 && r.Y >= 0 && r.X+r.W <= w && r.Y+r.H <= h
229}