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