refactor(ports): split client events and surface content into feature files
- commit
- 974d42e5954c84cae3d08f54c11a4a9b519f535b
- author
- brice · · 2026-10-08 05:31 UTC
- parent
- b727aa4
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
7 files · +449 -443
M internal/ports/client_events.go +0 -174
| @@ -110,30 +110,6 @@ type WindowActivate struct{ ID WindowID } | |||
| 110 | 110 | ||
| 111 | 111 | func (WindowActivate) clientEvent() {} | |
| 112 | 112 | ||
| 113 | - | // WorkspaceActivate requests showing workspaces by stable ID, in commit order. | |
| 114 | - | // Core applies the entire batch before publishing a new state. | |
| 115 | - | type WorkspaceActivate struct{ IDs []uint64 } | |
| 116 | - | ||
| 117 | - | func (WorkspaceActivate) clientEvent() {} | |
| 118 | - | ||
| 119 | - | // Workspaces is an immutable, latest-only inventory of output workspaces. | |
| 120 | - | type Workspaces struct{ Outputs []WorkspaceOutput } | |
| 121 | - | type WorkspaceOutput struct { | |
| 122 | - | Name string | |
| 123 | - | Workspaces []WorkspaceInfo | |
| 124 | - | } | |
| 125 | - | type WorkspaceInfo struct { | |
| 126 | - | ID uint64 | |
| 127 | - | Configured string // configured name; empty for dynamic workspaces. ID, not this, identifies the workspace | |
| 128 | - | Name string | |
| 129 | - | Index int // zero-based vertical coordinate | |
| 130 | - | Active, Hidden bool | |
| 131 | - | // Frame is the workspace viewport in output-local logical pixels | |
| 132 | - | // (Monitor.Frame geometry: the whole output unless the workspace has a | |
| 133 | - | // size override). It is where a capture session of it lies. | |
| 134 | - | Frame Rect | |
| 135 | - | } | |
| 136 | - | ||
| 137 | 113 | // WindowAppID carries wayland → core an app ID set after the window mapped. | |
| 138 | 114 | type WindowAppID struct { | |
| 139 | 115 | ID WindowID | |
| @@ -157,153 +133,3 @@ type WindowResized struct { | |||
| 157 | 133 | } | |
| 158 | 134 | ||
| 159 | 135 | func (WindowResized) clientEvent() {} | |
| 160 | - | ||
| 161 | - | // Popup anchors and gravities, as xdg_positioner values. | |
| 162 | - | const ( | |
| 163 | - | EdgeNone uint32 = iota | |
| 164 | - | EdgeTop | |
| 165 | - | EdgeBottom | |
| 166 | - | EdgeLeft | |
| 167 | - | EdgeRight | |
| 168 | - | EdgeTopLeft | |
| 169 | - | EdgeBottomLeft | |
| 170 | - | EdgeTopRight | |
| 171 | - | EdgeBottomRight | |
| 172 | - | ) | |
| 173 | - | ||
| 174 | - | // Popup constraint adjustments, as xdg_positioner values. | |
| 175 | - | const ( | |
| 176 | - | AdjustSlideX uint32 = 1 << iota | |
| 177 | - | AdjustSlideY | |
| 178 | - | AdjustFlipX | |
| 179 | - | AdjustFlipY | |
| 180 | - | AdjustResizeX | |
| 181 | - | AdjustResizeY | |
| 182 | - | ) | |
| 183 | - | ||
| 184 | - | // Positioner places a popup relative to its parent's window geometry: | |
| 185 | - | // the Anchor point of AnchorRect, moved by Offset, with the popup extending | |
| 186 | - | // towards Gravity. Adjust says how core may move it to stay on screen. | |
| 187 | - | type Positioner struct { | |
| 188 | - | Width, Height int | |
| 189 | - | AnchorRect Rect | |
| 190 | - | Anchor, Gravity uint32 | |
| 191 | - | OffsetX, OffsetY int | |
| 192 | - | Adjust uint32 | |
| 193 | - | } | |
| 194 | - | ||
| 195 | - | // PopupRequest carries wayland → core a popup to place (xdg_popup), on | |
| 196 | - | // its first commit and on reposition (Reposition, with the client's | |
| 197 | - | // Token). Parent is a window or popup. Grab popups take the keyboard and | |
| 198 | - | // close on a click elsewhere. | |
| 199 | - | type PopupRequest struct { | |
| 200 | - | ID, Parent WindowID | |
| 201 | - | Positioner Positioner | |
| 202 | - | Grab bool | |
| 203 | - | Reposition bool | |
| 204 | - | Token uint32 | |
| 205 | - | } | |
| 206 | - | ||
| 207 | - | func (PopupRequest) clientEvent() {} | |
| 208 | - | ||
| 209 | - | // PopupMapped carries wayland → core a placed popup that now has content. | |
| 210 | - | // It ends with WindowUnmapped. | |
| 211 | - | type PopupMapped struct{ ID WindowID } | |
| 212 | - | ||
| 213 | - | func (PopupMapped) clientEvent() {} | |
| 214 | - | ||
| 215 | - | // Layer is a wlr-layer-shell stacking layer. | |
| 216 | - | type Layer uint32 | |
| 217 | - | ||
| 218 | - | const ( | |
| 219 | - | LayerBackground Layer = iota | |
| 220 | - | LayerBottom | |
| 221 | - | LayerTop | |
| 222 | - | LayerOverlay | |
| 223 | - | ) | |
| 224 | - | ||
| 225 | - | // Anchor edges, as in zwlr_layer_surface_v1.anchor. | |
| 226 | - | const ( | |
| 227 | - | AnchorTop uint32 = 1 | |
| 228 | - | AnchorBottom uint32 = 2 | |
| 229 | - | AnchorLeft uint32 = 4 | |
| 230 | - | AnchorRight uint32 = 8 | |
| 231 | - | ) | |
| 232 | - | ||
| 233 | - | // LayerSurface is the committed state of one mapped layer surface. Width and | |
| 234 | - | // Height are the committed surface size in logical pixels. Margin is top, right, bottom, left. | |
| 235 | - | // IDs share the WindowID space with windows and never collide. | |
| 236 | - | type LayerSurface struct { | |
| 237 | - | ID WindowID | |
| 238 | - | Layer Layer | |
| 239 | - | Anchor uint32 | |
| 240 | - | ExclusiveZone int32 | |
| 241 | - | Margin [4]int32 | |
| 242 | - | Width, Height int | |
| 243 | - | Namespace string | |
| 244 | - | // Keyboard is the requested interactivity: 0 none, 1 exclusive, 2 on-demand. | |
| 245 | - | Keyboard uint32 | |
| 246 | - | // Output is the connector the surface is on. | |
| 247 | - | Output string | |
| 248 | - | } | |
| 249 | - | ||
| 250 | - | // LayerChanged carries wayland → core the full list of mapped layer surfaces, | |
| 251 | - | // sent whenever a layer surface maps, unmaps or commits new layer state. | |
| 252 | - | type LayerChanged struct{ Layers []LayerSurface } | |
| 253 | - | ||
| 254 | - | func (LayerChanged) clientEvent() {} | |
| 255 | - | ||
| 256 | - | // ConstraintMode is how a pointer constraint holds the pointer. | |
| 257 | - | type ConstraintMode uint8 | |
| 258 | - | ||
| 259 | - | const ( | |
| 260 | - | ConstraintNone ConstraintMode = iota | |
| 261 | - | // ConstraintLock keeps the pointer still: only relative motion flows. | |
| 262 | - | ConstraintLock | |
| 263 | - | // ConstraintConfine keeps the pointer inside Rect. | |
| 264 | - | ConstraintConfine | |
| 265 | - | ) | |
| 266 | - | ||
| 267 | - | // PointerConstraint is an active pointer constraint. Rect is logical: in | |
| 268 | - | // PointerConstrained it is window-local and empty means the whole window; | |
| 269 | - | // core sends input the resolved global rectangle, and in X, Y its cursor | |
| 270 | - | // position, where input holds a locked pointer. Warp moves input's | |
| 271 | - | // pointer to X, Y whatever the mode (wp_pointer_warp_v1). | |
| 272 | - | type PointerConstraint struct { | |
| 273 | - | Mode ConstraintMode | |
| 274 | - | Rect Rect | |
| 275 | - | X, Y float64 | |
| 276 | - | Warp bool | |
| 277 | - | } | |
| 278 | - | ||
| 279 | - | // Clamp keeps a global point inside the rectangle of a lock or confine; | |
| 280 | - | // no constraint and an empty rectangle leave it unchanged. | |
| 281 | - | func (c PointerConstraint) Clamp(x, y float64) (float64, float64) { | |
| 282 | - | if c.Mode == ConstraintNone || c.Rect.W <= 0 || c.Rect.H <= 0 { | |
| 283 | - | return x, y | |
| 284 | - | } | |
| 285 | - | x = min(max(x, float64(c.Rect.X)), float64(c.Rect.X+c.Rect.W-1)) | |
| 286 | - | y = min(max(y, float64(c.Rect.Y)), float64(c.Rect.Y+c.Rect.H-1)) | |
| 287 | - | return x, y | |
| 288 | - | } | |
| 289 | - | ||
| 290 | - | // PointerConstrained carries wayland → core the constraint active on a | |
| 291 | - | // window (zwp_pointer_constraints_v1); ID 0 means none is active. A | |
| 292 | - | // constraint is active only on the window with both pointer and keyboard | |
| 293 | - | // focus. | |
| 294 | - | type PointerConstrained struct { | |
| 295 | - | ID WindowID | |
| 296 | - | PointerConstraint | |
| 297 | - | } | |
| 298 | - | ||
| 299 | - | func (PointerConstrained) clientEvent() {} | |
| 300 | - | ||
| 301 | - | // PointerWarp carries wayland → core a client's request to move the | |
| 302 | - | // pointer to X, Y, window-local logical (wp_pointer_warp_v1). Wayland has | |
| 303 | - | // checked that the window has the pointer and the point is on it. | |
| 304 | - | type PointerWarp struct { | |
| 305 | - | ID WindowID | |
| 306 | - | X, Y float64 | |
| 307 | - | } | |
| 308 | - | ||
| 309 | - | func (PointerWarp) clientEvent() {} | |
| @@ -110,30 +110,6 @@ type WindowActivate struct{ ID WindowID } | |||||
| 110 | 110 | ||||
| 111 | func (WindowActivate) clientEvent() {} | 111 | func (WindowActivate) clientEvent() {} | ||
| 112 | 112 | ||||
| 113 | - | // WorkspaceActivate requests showing workspaces by stable ID, in commit order. | |||
| 114 | - | // Core applies the entire batch before publishing a new state. | |||
| 115 | - | type WorkspaceActivate struct{ IDs []uint64 } | |||
| 116 | - | ||||
| 117 | - | func (WorkspaceActivate) clientEvent() {} | |||
| 118 | - | ||||
| 119 | - | // Workspaces is an immutable, latest-only inventory of output workspaces. | |||
| 120 | - | type Workspaces struct{ Outputs []WorkspaceOutput } | |||
| 121 | - | type WorkspaceOutput struct { | |||
| 122 | - | Name string | |||
| 123 | - | Workspaces []WorkspaceInfo | |||
| 124 | - | } | |||
| 125 | - | type WorkspaceInfo struct { | |||
| 126 | - | ID uint64 | |||
| 127 | - | Configured string // configured name; empty for dynamic workspaces. ID, not this, identifies the workspace | |||
| 128 | - | Name string | |||
| 129 | - | Index int // zero-based vertical coordinate | |||
| 130 | - | Active, Hidden bool | |||
| 131 | - | // Frame is the workspace viewport in output-local logical pixels | |||
| 132 | - | // (Monitor.Frame geometry: the whole output unless the workspace has a | |||
| 133 | - | // size override). It is where a capture session of it lies. | |||
| 134 | - | Frame Rect | |||
| 135 | - | } | |||
| 136 | - | ||||
| 137 | // WindowAppID carries wayland → core an app ID set after the window mapped. | 113 | // WindowAppID carries wayland → core an app ID set after the window mapped. | ||
| 138 | type WindowAppID struct { | 114 | type WindowAppID struct { | ||
| 139 | ID WindowID | 115 | ID WindowID | ||
| @@ -157,153 +133,3 @@ type WindowResized struct { | |||||
| 157 | } | 133 | } | ||
| 158 | 134 | ||||
| 159 | func (WindowResized) clientEvent() {} | 135 | func (WindowResized) clientEvent() {} | ||
| 160 | - | ||||
| 161 | - | // Popup anchors and gravities, as xdg_positioner values. | |||
| 162 | - | const ( | |||
| 163 | - | EdgeNone uint32 = iota | |||
| 164 | - | EdgeTop | |||
| 165 | - | EdgeBottom | |||
| 166 | - | EdgeLeft | |||
| 167 | - | EdgeRight | |||
| 168 | - | EdgeTopLeft | |||
| 169 | - | EdgeBottomLeft | |||
| 170 | - | EdgeTopRight | |||
| 171 | - | EdgeBottomRight | |||
| 172 | - | ) | |||
| 173 | - | ||||
| 174 | - | // Popup constraint adjustments, as xdg_positioner values. | |||
| 175 | - | const ( | |||
| 176 | - | AdjustSlideX uint32 = 1 << iota | |||
| 177 | - | AdjustSlideY | |||
| 178 | - | AdjustFlipX | |||
| 179 | - | AdjustFlipY | |||
| 180 | - | AdjustResizeX | |||
| 181 | - | AdjustResizeY | |||
| 182 | - | ) | |||
| 183 | - | ||||
| 184 | - | // Positioner places a popup relative to its parent's window geometry: | |||
| 185 | - | // the Anchor point of AnchorRect, moved by Offset, with the popup extending | |||
| 186 | - | // towards Gravity. Adjust says how core may move it to stay on screen. | |||
| 187 | - | type Positioner struct { | |||
| 188 | - | Width, Height int | |||
| 189 | - | AnchorRect Rect | |||
| 190 | - | Anchor, Gravity uint32 | |||
| 191 | - | OffsetX, OffsetY int | |||
| 192 | - | Adjust uint32 | |||
| 193 | - | } | |||
| 194 | - | ||||
| 195 | - | // PopupRequest carries wayland → core a popup to place (xdg_popup), on | |||
| 196 | - | // its first commit and on reposition (Reposition, with the client's | |||
| 197 | - | // Token). Parent is a window or popup. Grab popups take the keyboard and | |||
| 198 | - | // close on a click elsewhere. | |||
| 199 | - | type PopupRequest struct { | |||
| 200 | - | ID, Parent WindowID | |||
| 201 | - | Positioner Positioner | |||
| 202 | - | Grab bool | |||
| 203 | - | Reposition bool | |||
| 204 | - | Token uint32 | |||
| 205 | - | } | |||
| 206 | - | ||||
| 207 | - | func (PopupRequest) clientEvent() {} | |||
| 208 | - | ||||
| 209 | - | // PopupMapped carries wayland → core a placed popup that now has content. | |||
| 210 | - | // It ends with WindowUnmapped. | |||
| 211 | - | type PopupMapped struct{ ID WindowID } | |||
| 212 | - | ||||
| 213 | - | func (PopupMapped) clientEvent() {} | |||
| 214 | - | ||||
| 215 | - | // Layer is a wlr-layer-shell stacking layer. | |||
| 216 | - | type Layer uint32 | |||
| 217 | - | ||||
| 218 | - | const ( | |||
| 219 | - | LayerBackground Layer = iota | |||
| 220 | - | LayerBottom | |||
| 221 | - | LayerTop | |||
| 222 | - | LayerOverlay | |||
| 223 | - | ) | |||
| 224 | - | ||||
| 225 | - | // Anchor edges, as in zwlr_layer_surface_v1.anchor. | |||
| 226 | - | const ( | |||
| 227 | - | AnchorTop uint32 = 1 | |||
| 228 | - | AnchorBottom uint32 = 2 | |||
| 229 | - | AnchorLeft uint32 = 4 | |||
| 230 | - | AnchorRight uint32 = 8 | |||
| 231 | - | ) | |||
| 232 | - | ||||
| 233 | - | // LayerSurface is the committed state of one mapped layer surface. Width and | |||
| 234 | - | // Height are the committed surface size in logical pixels. Margin is top, right, bottom, left. | |||
| 235 | - | // IDs share the WindowID space with windows and never collide. | |||
| 236 | - | type LayerSurface struct { | |||
| 237 | - | ID WindowID | |||
| 238 | - | Layer Layer | |||
| 239 | - | Anchor uint32 | |||
| 240 | - | ExclusiveZone int32 | |||
| 241 | - | Margin [4]int32 | |||
| 242 | - | Width, Height int | |||
| 243 | - | Namespace string | |||
| 244 | - | // Keyboard is the requested interactivity: 0 none, 1 exclusive, 2 on-demand. | |||
| 245 | - | Keyboard uint32 | |||
| 246 | - | // Output is the connector the surface is on. | |||
| 247 | - | Output string | |||
| 248 | - | } | |||
| 249 | - | ||||
| 250 | - | // LayerChanged carries wayland → core the full list of mapped layer surfaces, | |||
| 251 | - | // sent whenever a layer surface maps, unmaps or commits new layer state. | |||
| 252 | - | type LayerChanged struct{ Layers []LayerSurface } | |||
| 253 | - | ||||
| 254 | - | func (LayerChanged) clientEvent() {} | |||
| 255 | - | ||||
| 256 | - | // ConstraintMode is how a pointer constraint holds the pointer. | |||
| 257 | - | type ConstraintMode uint8 | |||
| 258 | - | ||||
| 259 | - | const ( | |||
| 260 | - | ConstraintNone ConstraintMode = iota | |||
| 261 | - | // ConstraintLock keeps the pointer still: only relative motion flows. | |||
| 262 | - | ConstraintLock | |||
| 263 | - | // ConstraintConfine keeps the pointer inside Rect. | |||
| 264 | - | ConstraintConfine | |||
| 265 | - | ) | |||
| 266 | - | ||||
| 267 | - | // PointerConstraint is an active pointer constraint. Rect is logical: in | |||
| 268 | - | // PointerConstrained it is window-local and empty means the whole window; | |||
| 269 | - | // core sends input the resolved global rectangle, and in X, Y its cursor | |||
| 270 | - | // position, where input holds a locked pointer. Warp moves input's | |||
| 271 | - | // pointer to X, Y whatever the mode (wp_pointer_warp_v1). | |||
| 272 | - | type PointerConstraint struct { | |||
| 273 | - | Mode ConstraintMode | |||
| 274 | - | Rect Rect | |||
| 275 | - | X, Y float64 | |||
| 276 | - | Warp bool | |||
| 277 | - | } | |||
| 278 | - | ||||
| 279 | - | // Clamp keeps a global point inside the rectangle of a lock or confine; | |||
| 280 | - | // no constraint and an empty rectangle leave it unchanged. | |||
| 281 | - | func (c PointerConstraint) Clamp(x, y float64) (float64, float64) { | |||
| 282 | - | if c.Mode == ConstraintNone || c.Rect.W <= 0 || c.Rect.H <= 0 { | |||
| 283 | - | return x, y | |||
| 284 | - | } | |||
| 285 | - | x = min(max(x, float64(c.Rect.X)), float64(c.Rect.X+c.Rect.W-1)) | |||
| 286 | - | y = min(max(y, float64(c.Rect.Y)), float64(c.Rect.Y+c.Rect.H-1)) | |||
| 287 | - | return x, y | |||
| 288 | - | } | |||
| 289 | - | ||||
| 290 | - | // PointerConstrained carries wayland → core the constraint active on a | |||
| 291 | - | // window (zwp_pointer_constraints_v1); ID 0 means none is active. A | |||
| 292 | - | // constraint is active only on the window with both pointer and keyboard | |||
| 293 | - | // focus. | |||
| 294 | - | type PointerConstrained struct { | |||
| 295 | - | ID WindowID | |||
| 296 | - | PointerConstraint | |||
| 297 | - | } | |||
| 298 | - | ||||
| 299 | - | func (PointerConstrained) clientEvent() {} | |||
| 300 | - | ||||
| 301 | - | // PointerWarp carries wayland → core a client's request to move the | |||
| 302 | - | // pointer to X, Y, window-local logical (wp_pointer_warp_v1). Wayland has | |||
| 303 | - | // checked that the window has the pointer and the point is on it. | |||
| 304 | - | type PointerWarp struct { | |||
| 305 | - | ID WindowID | |||
| 306 | - | X, Y float64 | |||
| 307 | - | } | |||
| 308 | - | ||||
| 309 | - | func (PointerWarp) clientEvent() {} | |||
A internal/ports/layer_events.go +42 -0
| @@ -0,0 +1,42 @@ | |||
| 1 | + | package ports | |
| 2 | + | ||
| 3 | + | // Layer is a wlr-layer-shell stacking layer. | |
| 4 | + | type Layer uint32 | |
| 5 | + | ||
| 6 | + | const ( | |
| 7 | + | LayerBackground Layer = iota | |
| 8 | + | LayerBottom | |
| 9 | + | LayerTop | |
| 10 | + | LayerOverlay | |
| 11 | + | ) | |
| 12 | + | ||
| 13 | + | // Anchor edges, as in zwlr_layer_surface_v1.anchor. | |
| 14 | + | const ( | |
| 15 | + | AnchorTop uint32 = 1 | |
| 16 | + | AnchorBottom uint32 = 2 | |
| 17 | + | AnchorLeft uint32 = 4 | |
| 18 | + | AnchorRight uint32 = 8 | |
| 19 | + | ) | |
| 20 | + | ||
| 21 | + | // LayerSurface is the committed state of one mapped layer surface. Width and | |
| 22 | + | // Height are the committed surface size in logical pixels. Margin is top, right, bottom, left. | |
| 23 | + | // IDs share the WindowID space with windows and never collide. | |
| 24 | + | type LayerSurface struct { | |
| 25 | + | ID WindowID | |
| 26 | + | Layer Layer | |
| 27 | + | Anchor uint32 | |
| 28 | + | ExclusiveZone int32 | |
| 29 | + | Margin [4]int32 | |
| 30 | + | Width, Height int | |
| 31 | + | Namespace string | |
| 32 | + | // Keyboard is the requested interactivity: 0 none, 1 exclusive, 2 on-demand. | |
| 33 | + | Keyboard uint32 | |
| 34 | + | // Output is the connector the surface is on. | |
| 35 | + | Output string | |
| 36 | + | } | |
| 37 | + | ||
| 38 | + | // LayerChanged carries wayland → core the full list of mapped layer surfaces, | |
| 39 | + | // sent whenever a layer surface maps, unmaps or commits new layer state. | |
| 40 | + | type LayerChanged struct{ Layers []LayerSurface } | |
| 41 | + | ||
| 42 | + | func (LayerChanged) clientEvent() {} | |
| @@ -0,0 +1,42 @@ | |||||
| 1 | + | package ports | |||
| 2 | + | ||||
| 3 | + | // Layer is a wlr-layer-shell stacking layer. | |||
| 4 | + | type Layer uint32 | |||
| 5 | + | ||||
| 6 | + | const ( | |||
| 7 | + | LayerBackground Layer = iota | |||
| 8 | + | LayerBottom | |||
| 9 | + | LayerTop | |||
| 10 | + | LayerOverlay | |||
| 11 | + | ) | |||
| 12 | + | ||||
| 13 | + | // Anchor edges, as in zwlr_layer_surface_v1.anchor. | |||
| 14 | + | const ( | |||
| 15 | + | AnchorTop uint32 = 1 | |||
| 16 | + | AnchorBottom uint32 = 2 | |||
| 17 | + | AnchorLeft uint32 = 4 | |||
| 18 | + | AnchorRight uint32 = 8 | |||
| 19 | + | ) | |||
| 20 | + | ||||
| 21 | + | // LayerSurface is the committed state of one mapped layer surface. Width and | |||
| 22 | + | // Height are the committed surface size in logical pixels. Margin is top, right, bottom, left. | |||
| 23 | + | // IDs share the WindowID space with windows and never collide. | |||
| 24 | + | type LayerSurface struct { | |||
| 25 | + | ID WindowID | |||
| 26 | + | Layer Layer | |||
| 27 | + | Anchor uint32 | |||
| 28 | + | ExclusiveZone int32 | |||
| 29 | + | Margin [4]int32 | |||
| 30 | + | Width, Height int | |||
| 31 | + | Namespace string | |||
| 32 | + | // Keyboard is the requested interactivity: 0 none, 1 exclusive, 2 on-demand. | |||
| 33 | + | Keyboard uint32 | |||
| 34 | + | // Output is the connector the surface is on. | |||
| 35 | + | Output string | |||
| 36 | + | } | |||
| 37 | + | ||||
| 38 | + | // LayerChanged carries wayland → core the full list of mapped layer surfaces, | |||
| 39 | + | // sent whenever a layer surface maps, unmaps or commits new layer state. | |||
| 40 | + | type LayerChanged struct{ Layers []LayerSurface } | |||
| 41 | + | ||||
| 42 | + | func (LayerChanged) clientEvent() {} | |||
A internal/ports/pointer_events.go +56 -0
| @@ -0,0 +1,56 @@ | |||
| 1 | + | package ports | |
| 2 | + | ||
| 3 | + | // ConstraintMode is how a pointer constraint holds the pointer. | |
| 4 | + | type ConstraintMode uint8 | |
| 5 | + | ||
| 6 | + | const ( | |
| 7 | + | ConstraintNone ConstraintMode = iota | |
| 8 | + | // ConstraintLock keeps the pointer still: only relative motion flows. | |
| 9 | + | ConstraintLock | |
| 10 | + | // ConstraintConfine keeps the pointer inside Rect. | |
| 11 | + | ConstraintConfine | |
| 12 | + | ) | |
| 13 | + | ||
| 14 | + | // PointerConstraint is an active pointer constraint. Rect is logical: in | |
| 15 | + | // PointerConstrained it is window-local and empty means the whole window; | |
| 16 | + | // core sends input the resolved global rectangle, and in X, Y its cursor | |
| 17 | + | // position, where input holds a locked pointer. Warp moves input's | |
| 18 | + | // pointer to X, Y whatever the mode (wp_pointer_warp_v1). | |
| 19 | + | type PointerConstraint struct { | |
| 20 | + | Mode ConstraintMode | |
| 21 | + | Rect Rect | |
| 22 | + | X, Y float64 | |
| 23 | + | Warp bool | |
| 24 | + | } | |
| 25 | + | ||
| 26 | + | // Clamp keeps a global point inside the rectangle of a lock or confine; | |
| 27 | + | // no constraint and an empty rectangle leave it unchanged. | |
| 28 | + | func (c PointerConstraint) Clamp(x, y float64) (float64, float64) { | |
| 29 | + | if c.Mode == ConstraintNone || c.Rect.W <= 0 || c.Rect.H <= 0 { | |
| 30 | + | return x, y | |
| 31 | + | } | |
| 32 | + | x = min(max(x, float64(c.Rect.X)), float64(c.Rect.X+c.Rect.W-1)) | |
| 33 | + | y = min(max(y, float64(c.Rect.Y)), float64(c.Rect.Y+c.Rect.H-1)) | |
| 34 | + | return x, y | |
| 35 | + | } | |
| 36 | + | ||
| 37 | + | // PointerConstrained carries wayland → core the constraint active on a | |
| 38 | + | // window (zwp_pointer_constraints_v1); ID 0 means none is active. A | |
| 39 | + | // constraint is active only on the window with both pointer and keyboard | |
| 40 | + | // focus. | |
| 41 | + | type PointerConstrained struct { | |
| 42 | + | ID WindowID | |
| 43 | + | PointerConstraint | |
| 44 | + | } | |
| 45 | + | ||
| 46 | + | func (PointerConstrained) clientEvent() {} | |
| 47 | + | ||
| 48 | + | // PointerWarp carries wayland → core a client's request to move the | |
| 49 | + | // pointer to X, Y, window-local logical (wp_pointer_warp_v1). Wayland has | |
| 50 | + | // checked that the window has the pointer and the point is on it. | |
| 51 | + | type PointerWarp struct { | |
| 52 | + | ID WindowID | |
| 53 | + | X, Y float64 | |
| 54 | + | } | |
| 55 | + | ||
| 56 | + | func (PointerWarp) clientEvent() {} | |
| @@ -0,0 +1,56 @@ | |||||
| 1 | + | package ports | |||
| 2 | + | ||||
| 3 | + | // ConstraintMode is how a pointer constraint holds the pointer. | |||
| 4 | + | type ConstraintMode uint8 | |||
| 5 | + | ||||
| 6 | + | const ( | |||
| 7 | + | ConstraintNone ConstraintMode = iota | |||
| 8 | + | // ConstraintLock keeps the pointer still: only relative motion flows. | |||
| 9 | + | ConstraintLock | |||
| 10 | + | // ConstraintConfine keeps the pointer inside Rect. | |||
| 11 | + | ConstraintConfine | |||
| 12 | + | ) | |||
| 13 | + | ||||
| 14 | + | // PointerConstraint is an active pointer constraint. Rect is logical: in | |||
| 15 | + | // PointerConstrained it is window-local and empty means the whole window; | |||
| 16 | + | // core sends input the resolved global rectangle, and in X, Y its cursor | |||
| 17 | + | // position, where input holds a locked pointer. Warp moves input's | |||
| 18 | + | // pointer to X, Y whatever the mode (wp_pointer_warp_v1). | |||
| 19 | + | type PointerConstraint struct { | |||
| 20 | + | Mode ConstraintMode | |||
| 21 | + | Rect Rect | |||
| 22 | + | X, Y float64 | |||
| 23 | + | Warp bool | |||
| 24 | + | } | |||
| 25 | + | ||||
| 26 | + | // Clamp keeps a global point inside the rectangle of a lock or confine; | |||
| 27 | + | // no constraint and an empty rectangle leave it unchanged. | |||
| 28 | + | func (c PointerConstraint) Clamp(x, y float64) (float64, float64) { | |||
| 29 | + | if c.Mode == ConstraintNone || c.Rect.W <= 0 || c.Rect.H <= 0 { | |||
| 30 | + | return x, y | |||
| 31 | + | } | |||
| 32 | + | x = min(max(x, float64(c.Rect.X)), float64(c.Rect.X+c.Rect.W-1)) | |||
| 33 | + | y = min(max(y, float64(c.Rect.Y)), float64(c.Rect.Y+c.Rect.H-1)) | |||
| 34 | + | return x, y | |||
| 35 | + | } | |||
| 36 | + | ||||
| 37 | + | // PointerConstrained carries wayland → core the constraint active on a | |||
| 38 | + | // window (zwp_pointer_constraints_v1); ID 0 means none is active. A | |||
| 39 | + | // constraint is active only on the window with both pointer and keyboard | |||
| 40 | + | // focus. | |||
| 41 | + | type PointerConstrained struct { | |||
| 42 | + | ID WindowID | |||
| 43 | + | PointerConstraint | |||
| 44 | + | } | |||
| 45 | + | ||||
| 46 | + | func (PointerConstrained) clientEvent() {} | |||
| 47 | + | ||||
| 48 | + | // PointerWarp carries wayland → core a client's request to move the | |||
| 49 | + | // pointer to X, Y, window-local logical (wp_pointer_warp_v1). Wayland has | |||
| 50 | + | // checked that the window has the pointer and the point is on it. | |||
| 51 | + | type PointerWarp struct { | |||
| 52 | + | ID WindowID | |||
| 53 | + | X, Y float64 | |||
| 54 | + | } | |||
| 55 | + | ||||
| 56 | + | func (PointerWarp) clientEvent() {} | |||
A internal/ports/popup_events.go +55 -0
| @@ -0,0 +1,55 @@ | |||
| 1 | + | package ports | |
| 2 | + | ||
| 3 | + | // Popup anchors and gravities, as xdg_positioner values. | |
| 4 | + | const ( | |
| 5 | + | EdgeNone uint32 = iota | |
| 6 | + | EdgeTop | |
| 7 | + | EdgeBottom | |
| 8 | + | EdgeLeft | |
| 9 | + | EdgeRight | |
| 10 | + | EdgeTopLeft | |
| 11 | + | EdgeBottomLeft | |
| 12 | + | EdgeTopRight | |
| 13 | + | EdgeBottomRight | |
| 14 | + | ) | |
| 15 | + | ||
| 16 | + | // Popup constraint adjustments, as xdg_positioner values. | |
| 17 | + | const ( | |
| 18 | + | AdjustSlideX uint32 = 1 << iota | |
| 19 | + | AdjustSlideY | |
| 20 | + | AdjustFlipX | |
| 21 | + | AdjustFlipY | |
| 22 | + | AdjustResizeX | |
| 23 | + | AdjustResizeY | |
| 24 | + | ) | |
| 25 | + | ||
| 26 | + | // Positioner places a popup relative to its parent's window geometry: | |
| 27 | + | // the Anchor point of AnchorRect, moved by Offset, with the popup extending | |
| 28 | + | // towards Gravity. Adjust says how core may move it to stay on screen. | |
| 29 | + | type Positioner struct { | |
| 30 | + | Width, Height int | |
| 31 | + | AnchorRect Rect | |
| 32 | + | Anchor, Gravity uint32 | |
| 33 | + | OffsetX, OffsetY int | |
| 34 | + | Adjust uint32 | |
| 35 | + | } | |
| 36 | + | ||
| 37 | + | // PopupRequest carries wayland → core a popup to place (xdg_popup), on | |
| 38 | + | // its first commit and on reposition (Reposition, with the client's | |
| 39 | + | // Token). Parent is a window or popup. Grab popups take the keyboard and | |
| 40 | + | // close on a click elsewhere. | |
| 41 | + | type PopupRequest struct { | |
| 42 | + | ID, Parent WindowID | |
| 43 | + | Positioner Positioner | |
| 44 | + | Grab bool | |
| 45 | + | Reposition bool | |
| 46 | + | Token uint32 | |
| 47 | + | } | |
| 48 | + | ||
| 49 | + | func (PopupRequest) clientEvent() {} | |
| 50 | + | ||
| 51 | + | // PopupMapped carries wayland → core a placed popup that now has content. | |
| 52 | + | // It ends with WindowUnmapped. | |
| 53 | + | type PopupMapped struct{ ID WindowID } | |
| 54 | + | ||
| 55 | + | func (PopupMapped) clientEvent() {} | |
| @@ -0,0 +1,55 @@ | |||||
| 1 | + | package ports | |||
| 2 | + | ||||
| 3 | + | // Popup anchors and gravities, as xdg_positioner values. | |||
| 4 | + | const ( | |||
| 5 | + | EdgeNone uint32 = iota | |||
| 6 | + | EdgeTop | |||
| 7 | + | EdgeBottom | |||
| 8 | + | EdgeLeft | |||
| 9 | + | EdgeRight | |||
| 10 | + | EdgeTopLeft | |||
| 11 | + | EdgeBottomLeft | |||
| 12 | + | EdgeTopRight | |||
| 13 | + | EdgeBottomRight | |||
| 14 | + | ) | |||
| 15 | + | ||||
| 16 | + | // Popup constraint adjustments, as xdg_positioner values. | |||
| 17 | + | const ( | |||
| 18 | + | AdjustSlideX uint32 = 1 << iota | |||
| 19 | + | AdjustSlideY | |||
| 20 | + | AdjustFlipX | |||
| 21 | + | AdjustFlipY | |||
| 22 | + | AdjustResizeX | |||
| 23 | + | AdjustResizeY | |||
| 24 | + | ) | |||
| 25 | + | ||||
| 26 | + | // Positioner places a popup relative to its parent's window geometry: | |||
| 27 | + | // the Anchor point of AnchorRect, moved by Offset, with the popup extending | |||
| 28 | + | // towards Gravity. Adjust says how core may move it to stay on screen. | |||
| 29 | + | type Positioner struct { | |||
| 30 | + | Width, Height int | |||
| 31 | + | AnchorRect Rect | |||
| 32 | + | Anchor, Gravity uint32 | |||
| 33 | + | OffsetX, OffsetY int | |||
| 34 | + | Adjust uint32 | |||
| 35 | + | } | |||
| 36 | + | ||||
| 37 | + | // PopupRequest carries wayland → core a popup to place (xdg_popup), on | |||
| 38 | + | // its first commit and on reposition (Reposition, with the client's | |||
| 39 | + | // Token). Parent is a window or popup. Grab popups take the keyboard and | |||
| 40 | + | // close on a click elsewhere. | |||
| 41 | + | type PopupRequest struct { | |||
| 42 | + | ID, Parent WindowID | |||
| 43 | + | Positioner Positioner | |||
| 44 | + | Grab bool | |||
| 45 | + | Reposition bool | |||
| 46 | + | Token uint32 | |||
| 47 | + | } | |||
| 48 | + | ||||
| 49 | + | func (PopupRequest) clientEvent() {} | |||
| 50 | + | ||||
| 51 | + | // PopupMapped carries wayland → core a placed popup that now has content. | |||
| 52 | + | // It ends with WindowUnmapped. | |||
| 53 | + | type PopupMapped struct{ ID WindowID } | |||
| 54 | + | ||||
| 55 | + | func (PopupMapped) clientEvent() {} | |||
M internal/ports/scene.go +1 -269
| @@ -1,11 +1,6 @@ | |||
| 1 | 1 | package ports | |
| 2 | 2 | ||
| 3 | - | import ( | |
| 4 | - | "image" | |
| 5 | - | "math" | |
| 6 | - | "os" | |
| 7 | - | "slices" | |
| 8 | - | ) | |
| 3 | + | import "slices" | |
| 9 | 4 | ||
| 10 | 5 | // Scene carries core → renderer immutable snapshots with fresh Windows slices, | |
| 11 | 6 | // one per output. Rects and the output size are logical and local to the | |
| @@ -142,269 +137,6 @@ type SceneLayer struct { | |||
| 142 | 137 | Rect Rect | |
| 143 | 138 | } | |
| 144 | 139 | ||
| 145 | - | // SurfaceColor is the committed encoding of a surface. Zero means sRGB. | |
| 146 | - | // PQ+BT.2020 and extended-linear sRGB are color-managed; zero is legacy | |
| 147 | - | // sRGB. MaxCLL/MaxFALL are in nits. YUV metadata is per surface commit. | |
| 148 | - | type SurfaceColor struct { | |
| 149 | - | TF, Primaries uint8 | |
| 150 | - | MaxCLL, MaxFALL uint16 | |
| 151 | - | // Coefficients/range follow wp_color_representation_v1 (zero = | |
| 152 | - | // unspecified, for YUV interpreted as BT.709 limited). Chroma is a | |
| 153 | - | // H.273 Chroma420SampleLocType + 1 (zero defaults to type 0). | |
| 154 | - | Coefficients, Range, Chroma uint8 | |
| 155 | - | } | |
| 156 | - | ||
| 157 | - | const ( | |
| 158 | - | ColorTFPQ uint8 = 11 // wp_color_manager_v1 ST2084 PQ | |
| 159 | - | ColorTFExtendedLinear uint8 = 5 // wp_color_manager_v1 ext_linear | |
| 160 | - | ColorPrimariesBT2020 uint8 = 6 // wp_color_manager_v1 BT.2020 | |
| 161 | - | ColorPrimariesSRGB uint8 = 1 | |
| 162 | - | ColorTFSRGB uint8 = 9 // wp_color_manager_v1 sRGB | |
| 163 | - | ) | |
| 164 | - | ||
| 165 | - | func (c SurfaceColor) IsPQ2020() bool { | |
| 166 | - | return c.TF == ColorTFPQ && c.Primaries == ColorPrimariesBT2020 | |
| 167 | - | } | |
| 168 | - | ||
| 169 | - | func (c SurfaceColor) IsExtendedLinear() bool { | |
| 170 | - | return c.TF == ColorTFExtendedLinear && c.Primaries == ColorPrimariesSRGB | |
| 171 | - | } | |
| 172 | - | ||
| 173 | - | // ContentType is the committed wp_content_type_v1 hint. | |
| 174 | - | type ContentType uint8 | |
| 175 | - | ||
| 176 | - | const ( | |
| 177 | - | ContentNone ContentType = iota | |
| 178 | - | ContentPhoto | |
| 179 | - | ContentVideo | |
| 180 | - | ContentGame | |
| 181 | - | ) | |
| 182 | - | ||
| 183 | - | // SurfaceContent carries wayland → output the latest committed content of a | |
| 184 | - | // window: SHM, a client shared-memory buffer, DMABuf, a GPU buffer, or | |
| 185 | - | // Solid, a single-pixel buffer. Renderers read them in place and never modify | |
| 186 | - | // them. Empty means the window | |
| 187 | - | // has no content. LogicalW and LogicalH are the surface size in logical | |
| 188 | - | // pixels (buffer scale and viewport applied). Subsurfaces come in Children; | |
| 189 | - | // Geometry is the part of the surface that is the window (xdg window | |
| 190 | - | // geometry), the rest being client shadows. | |
| 191 | - | type SurfaceContent struct { | |
| 192 | - | ID WindowID | |
| 193 | - | ContentType ContentType | |
| 194 | - | // Surface is a never-reused wl_surface identity; Version identifies its | |
| 195 | - | // applied buffer content independently of the window publication Seq. | |
| 196 | - | Surface, Version uint64 | |
| 197 | - | // Seq counts the window's contents (wayland sets it), for release. | |
| 198 | - | Seq uint64 | |
| 199 | - | Width, Height int | |
| 200 | - | LogicalW, LogicalH int | |
| 201 | - | // Source is the committed crop in buffer pixels: x, y, width, height. | |
| 202 | - | // Zero width means the entire buffer is used. | |
| 203 | - | Source [4]float32 | |
| 204 | - | // Transform is the wl_output.transform the client applied to the | |
| 205 | - | // buffer: readers apply its inverse. Width and Height stay the buffer's. | |
| 206 | - | Transform BufferTransform | |
| 207 | - | // Opaque ignores the alpha byte: an x format, or an opaque region that | |
| 208 | - | // covers the whole surface. It is false while Fade is set. | |
| 209 | - | Opaque bool | |
| 210 | - | // OpaqueRect is the largest rect of the opaque region inside a surface | |
| 211 | - | // that is not wholly Opaque, in logical pixels from the surface origin: | |
| 212 | - | // the pixels there ignore alpha too, so the surfaces below it are hidden | |
| 213 | - | // by it. Empty when there is none or while Fade is set. | |
| 214 | - | OpaqueRect Rect | |
| 215 | - | // Fade is how much wp_alpha_modifier_v1 fades the surface: 0 none, | |
| 216 | - | // 1 invisible. Its opacity is multiplied by 1-Fade. | |
| 217 | - | Fade float32 | |
| 218 | - | Color SurfaceColor | |
| 219 | - | SHM *SHMBuffer | |
| 220 | - | DMABuf *DMABuf | |
| 221 | - | // Solid is a single-pixel buffer (Width and Height are 1): the surface is | |
| 222 | - | // one flat color. | |
| 223 | - | Solid *SolidColor | |
| 224 | - | // Children are the subsurfaces, bottom to top, flattened. | |
| 225 | - | Children []Subsurface | |
| 226 | - | // Geometry is in logical pixels from the surface origin; empty means | |
| 227 | - | // the whole surface. | |
| 228 | - | Geometry Rect | |
| 229 | - | // Async asks for tearing presentation (wp_tearing_control_v1); outputs | |
| 230 | - | // honour it only in direct scanout. | |
| 231 | - | Async bool | |
| 232 | - | // Acquire is the explicit-sync fence of this content's root buffer | |
| 233 | - | // (wp_linux_drm_syncobj_v1): readers wait on it before reading the | |
| 234 | - | // buffer, instead of the buffer's implicit fences. Wayland owns it and | |
| 235 | - | // closes it when the buffer is released; a reader that keeps it past | |
| 236 | - | // the call duplicates it under Acquire.SyscallConn. nil: implicit sync. | |
| 237 | - | // Wayland closes it only after every output reported a later content of | |
| 238 | - | // the window as seen, so a reader never gets it already closed. | |
| 239 | - | Acquire *os.File | |
| 240 | - | // DamageHistory is what changed in the root surface's buffer over the | |
| 241 | - | // window's last contents, oldest first, ending with this one (Seq). | |
| 242 | - | // A renderer holding an older content redraws the union of the | |
| 243 | - | // entries after it, or everything when the history does not reach it. | |
| 244 | - | DamageHistory []SeqDamage | |
| 245 | - | } | |
| 246 | - | ||
| 247 | - | // BufferTransform is a wl_output.transform value: bit 0 rotates 90° | |
| 248 | - | // counter-clockwise, bit 1 rotates 180°, bit 2 flips around the vertical | |
| 249 | - | // axis first. | |
| 250 | - | type BufferTransform uint8 | |
| 251 | - | ||
| 252 | - | // Rotated reports whether width and height swap between buffer and surface. | |
| 253 | - | func (t BufferTransform) Rotated() bool { return t&1 != 0 } | |
| 254 | - | ||
| 255 | - | // Size returns the size of a w×h space seen through t: width and height swap | |
| 256 | - | // when t is Rotated. | |
| 257 | - | func (t BufferTransform) Size(w, h int) (int, int) { | |
| 258 | - | if t.Rotated() { | |
| 259 | - | return h, w | |
| 260 | - | } | |
| 261 | - | return w, h | |
| 262 | - | } | |
| 263 | - | ||
| 264 | - | // ToBuffer maps a point of a w×h surface (logical, before scale) to the | |
| 265 | - | // same point of its buffer, in the buffer's untransformed w'×h' axes, where | |
| 266 | - | // w' and h' swap when the transform is Rotated. | |
| 267 | - | func (t BufferTransform) ToBuffer(x, y, w, h float64) (float64, float64) { | |
| 268 | - | if t&4 != 0 { | |
| 269 | - | x = w - x | |
| 270 | - | } | |
| 271 | - | switch t & 3 { | |
| 272 | - | case 1: | |
| 273 | - | return y, w - x | |
| 274 | - | case 2: | |
| 275 | - | return w - x, h - y | |
| 276 | - | case 3: | |
| 277 | - | return h - y, x | |
| 278 | - | } | |
| 279 | - | return x, y | |
| 280 | - | } | |
| 281 | - | ||
| 282 | - | // Invert is the transform that maps target points back: for a w×h scene and | |
| 283 | - | // its w'×h' target, t.Invert().ToBuffer(t.ToBuffer(p, w, h), w', h') == p. | |
| 284 | - | func (t BufferTransform) Invert() BufferTransform { | |
| 285 | - | if t&1 != 0 && t&4 == 0 { | |
| 286 | - | return t ^ 2 | |
| 287 | - | } | |
| 288 | - | return t | |
| 289 | - | } | |
| 290 | - | ||
| 291 | - | // RectToBuffer maps a rectangle of a w×h surface to the buffer. Integer | |
| 292 | - | // rectangles stay integer under these transforms; rounding absorbs float noise. | |
| 293 | - | func (t BufferTransform) RectToBuffer(r image.Rectangle, w, h int) image.Rectangle { | |
| 294 | - | ax, ay := t.ToBuffer(float64(r.Min.X), float64(r.Min.Y), float64(w), float64(h)) | |
| 295 | - | bx, by := t.ToBuffer(float64(r.Max.X), float64(r.Max.Y), float64(w), float64(h)) | |
| 296 | - | return image.Rect(int(math.Round(ax)), int(math.Round(ay)), int(math.Round(bx)), int(math.Round(by))) | |
| 297 | - | } | |
| 298 | - | ||
| 299 | - | // SeqDamage is what content Seq changed from the one before: Rects in | |
| 300 | - | // root buffer pixels, or everything when Full. | |
| 301 | - | type SeqDamage struct { | |
| 302 | - | Seq uint64 | |
| 303 | - | Full bool | |
| 304 | - | Rects []Rect | |
| 305 | - | } | |
| 306 | - | ||
| 307 | - | // DamageSince is the union of changes after content seq up to this one, | |
| 308 | - | // and false when the history does not reach back to seq (redraw all). | |
| 309 | - | func (c SurfaceContent) DamageSince(seq uint64) ([]Rect, bool) { | |
| 310 | - | out, ok := c.AppendDamageSince(nil, seq) | |
| 311 | - | if !ok { | |
| 312 | - | return nil, false | |
| 313 | - | } | |
| 314 | - | return out, true | |
| 315 | - | } | |
| 316 | - | ||
| 317 | - | // AppendDamageSince is DamageSince appending to dst, so a caller reusing | |
| 318 | - | // its slice allocates nothing. When the history does not reach back to seq | |
| 319 | - | // it returns dst unchanged and false. | |
| 320 | - | func (c SurfaceContent) AppendDamageSince(dst []Rect, seq uint64) ([]Rect, bool) { | |
| 321 | - | if seq >= c.Seq { | |
| 322 | - | return dst, true | |
| 323 | - | } | |
| 324 | - | h := c.DamageHistory | |
| 325 | - | if len(h) == 0 || h[0].Seq > seq+1 || h[len(h)-1].Seq != c.Seq { | |
| 326 | - | return dst, false | |
| 327 | - | } | |
| 328 | - | n := len(dst) | |
| 329 | - | for _, d := range h { | |
| 330 | - | if d.Seq <= seq { | |
| 331 | - | continue | |
| 332 | - | } | |
| 333 | - | if d.Full { | |
| 334 | - | return dst[:n], false | |
| 335 | - | } | |
| 336 | - | dst = append(dst, d.Rects...) | |
| 337 | - | } | |
| 338 | - | return dst, true | |
| 339 | - | } | |
| 340 | - | ||
| 341 | - | // Subsurface is a child surface at X, Y logical pixels from the root | |
| 342 | - | // surface origin. Below children are drawn under the root surface. | |
| 343 | - | type Subsurface struct { | |
| 344 | - | X, Y int | |
| 345 | - | Below bool | |
| 346 | - | SurfaceContent | |
| 347 | - | } | |
| 348 | - | ||
| 349 | - | // HasBuffer reports whether the surface itself carries a client buffer. | |
| 350 | - | func (c SurfaceContent) HasBuffer() bool { | |
| 351 | - | return c.SHM != nil || c.DMABuf != nil || c.Solid != nil | |
| 352 | - | } | |
| 353 | - | ||
| 354 | - | // SolidColor is the color of a single-pixel buffer: premultiplied by alpha, | |
| 355 | - | // 0 to 1, sRGB-encoded like the pixels of client buffers. | |
| 356 | - | type SolidColor struct{ R, G, B, A float32 } | |
| 357 | - | ||
| 358 | - | // Empty reports whether the content has nothing to draw. | |
| 359 | - | func (c SurfaceContent) Empty() bool { | |
| 360 | - | return !c.HasBuffer() && len(c.Children) == 0 | |
| 361 | - | } | |
| 362 | - | ||
| 363 | - | // SHMBuffer is a client wl_shm buffer: B8G8R8A8 pixels (argb8888/xrgb8888 | |
| 364 | - | // little-endian) at Offset in the pool File, Stride bytes per row. The | |
| 365 | - | // client keeps writing the file, so renderers copy from it when they draw. | |
| 366 | - | // Pool is unique per wl_shm pool for the session: renderers map a pool | |
| 367 | - | // once. File stays open while the client's buffer exists and follows the | |
| 368 | - | // DMABuf rules: duplicate it under File.SyscallConn before keeping it. A | |
| 369 | - | // client may shrink the file at any time, so readers must survive faults. | |
| 370 | - | type SHMBuffer struct { | |
| 371 | - | Pool uint64 | |
| 372 | - | File *os.File | |
| 373 | - | Offset, Stride int | |
| 374 | - | } | |
| 375 | - | ||
| 376 | - | // DMABuf is a client GPU buffer (linux-dmabuf). Its files stay open while | |
| 377 | - | // the client's buffer exists; a renderer that keeps it must duplicate the | |
| 378 | - | // descriptors under File.SyscallConn so a concurrent close cannot hand it a | |
| 379 | - | // reused one. ID is unique per buffer for the whole session: renderers key | |
| 380 | - | // their imports on it. | |
| 381 | - | type DMABuf struct { | |
| 382 | - | ID uint64 | |
| 383 | - | Width, Height int | |
| 384 | - | Format uint32 // DRM fourcc | |
| 385 | - | Modifier uint64 | |
| 386 | - | Planes []DMABufPlane | |
| 387 | - | } | |
| 388 | - | ||
| 389 | - | // DMABufPlane is one plane of a DMABuf. | |
| 390 | - | type DMABufPlane struct { | |
| 391 | - | File *os.File | |
| 392 | - | Offset, Stride uint32 | |
| 393 | - | } | |
| 394 | - | ||
| 395 | - | // DMABufFormat is a DRM fourcc with a modifier the renderer can import. | |
| 396 | - | type DMABufFormat struct { | |
| 397 | - | Format uint32 | |
| 398 | - | Modifier uint64 | |
| 399 | - | } | |
| 400 | - | ||
| 401 | - | // DMABufSupport is what the renderer imports: the render device (a dev_t, | |
| 402 | - | // 0 when unknown) and the formats. No formats means no dmabuf. | |
| 403 | - | type DMABufSupport struct { | |
| 404 | - | Device uint64 | |
| 405 | - | Formats []DMABufFormat | |
| 406 | - | } | |
| 407 | - | ||
| 408 | 140 | // SceneWindow carries core → renderer window placement. | |
| 409 | 141 | type SceneWindow struct { | |
| 410 | 142 | ID WindowID | |
| @@ -1,11 +1,6 @@ | |||||
| 1 | package ports | 1 | package ports | ||
| 2 | 2 | ||||
| 3 | - | import ( | 3 | + | import "slices" |
| 4 | - | "image" | |||
| 5 | - | "math" | |||
| 6 | - | "os" | |||
| 7 | - | "slices" | |||
| 8 | - | ) | |||
| 9 | 4 | ||||
| 10 | // Scene carries core → renderer immutable snapshots with fresh Windows slices, | 5 | // Scene carries core → renderer immutable snapshots with fresh Windows slices, | ||
| 11 | // one per output. Rects and the output size are logical and local to the | 6 | // one per output. Rects and the output size are logical and local to the | ||
| @@ -142,269 +137,6 @@ type SceneLayer struct { | |||||
| 142 | Rect Rect | 137 | Rect Rect | ||
| 143 | } | 138 | } | ||
| 144 | 139 | ||||
| 145 | - | // SurfaceColor is the committed encoding of a surface. Zero means sRGB. | |||
| 146 | - | // PQ+BT.2020 and extended-linear sRGB are color-managed; zero is legacy | |||
| 147 | - | // sRGB. MaxCLL/MaxFALL are in nits. YUV metadata is per surface commit. | |||
| 148 | - | type SurfaceColor struct { | |||
| 149 | - | TF, Primaries uint8 | |||
| 150 | - | MaxCLL, MaxFALL uint16 | |||
| 151 | - | // Coefficients/range follow wp_color_representation_v1 (zero = | |||
| 152 | - | // unspecified, for YUV interpreted as BT.709 limited). Chroma is a | |||
| 153 | - | // H.273 Chroma420SampleLocType + 1 (zero defaults to type 0). | |||
| 154 | - | Coefficients, Range, Chroma uint8 | |||
| 155 | - | } | |||
| 156 | - | ||||
| 157 | - | const ( | |||
| 158 | - | ColorTFPQ uint8 = 11 // wp_color_manager_v1 ST2084 PQ | |||
| 159 | - | ColorTFExtendedLinear uint8 = 5 // wp_color_manager_v1 ext_linear | |||
| 160 | - | ColorPrimariesBT2020 uint8 = 6 // wp_color_manager_v1 BT.2020 | |||
| 161 | - | ColorPrimariesSRGB uint8 = 1 | |||
| 162 | - | ColorTFSRGB uint8 = 9 // wp_color_manager_v1 sRGB | |||
| 163 | - | ) | |||
| 164 | - | ||||
| 165 | - | func (c SurfaceColor) IsPQ2020() bool { | |||
| 166 | - | return c.TF == ColorTFPQ && c.Primaries == ColorPrimariesBT2020 | |||
| 167 | - | } | |||
| 168 | - | ||||
| 169 | - | func (c SurfaceColor) IsExtendedLinear() bool { | |||
| 170 | - | return c.TF == ColorTFExtendedLinear && c.Primaries == ColorPrimariesSRGB | |||
| 171 | - | } | |||
| 172 | - | ||||
| 173 | - | // ContentType is the committed wp_content_type_v1 hint. | |||
| 174 | - | type ContentType uint8 | |||
| 175 | - | ||||
| 176 | - | const ( | |||
| 177 | - | ContentNone ContentType = iota | |||
| 178 | - | ContentPhoto | |||
| 179 | - | ContentVideo | |||
| 180 | - | ContentGame | |||
| 181 | - | ) | |||
| 182 | - | ||||
| 183 | - | // SurfaceContent carries wayland → output the latest committed content of a | |||
| 184 | - | // window: SHM, a client shared-memory buffer, DMABuf, a GPU buffer, or | |||
| 185 | - | // Solid, a single-pixel buffer. Renderers read them in place and never modify | |||
| 186 | - | // them. Empty means the window | |||
| 187 | - | // has no content. LogicalW and LogicalH are the surface size in logical | |||
| 188 | - | // pixels (buffer scale and viewport applied). Subsurfaces come in Children; | |||
| 189 | - | // Geometry is the part of the surface that is the window (xdg window | |||
| 190 | - | // geometry), the rest being client shadows. | |||
| 191 | - | type SurfaceContent struct { | |||
| 192 | - | ID WindowID | |||
| 193 | - | ContentType ContentType | |||
| 194 | - | // Surface is a never-reused wl_surface identity; Version identifies its | |||
| 195 | - | // applied buffer content independently of the window publication Seq. | |||
| 196 | - | Surface, Version uint64 | |||
| 197 | - | // Seq counts the window's contents (wayland sets it), for release. | |||
| 198 | - | Seq uint64 | |||
| 199 | - | Width, Height int | |||
| 200 | - | LogicalW, LogicalH int | |||
| 201 | - | // Source is the committed crop in buffer pixels: x, y, width, height. | |||
| 202 | - | // Zero width means the entire buffer is used. | |||
| 203 | - | Source [4]float32 | |||
| 204 | - | // Transform is the wl_output.transform the client applied to the | |||
| 205 | - | // buffer: readers apply its inverse. Width and Height stay the buffer's. | |||
| 206 | - | Transform BufferTransform | |||
| 207 | - | // Opaque ignores the alpha byte: an x format, or an opaque region that | |||
| 208 | - | // covers the whole surface. It is false while Fade is set. | |||
| 209 | - | Opaque bool | |||
| 210 | - | // OpaqueRect is the largest rect of the opaque region inside a surface | |||
| 211 | - | // that is not wholly Opaque, in logical pixels from the surface origin: | |||
| 212 | - | // the pixels there ignore alpha too, so the surfaces below it are hidden | |||
| 213 | - | // by it. Empty when there is none or while Fade is set. | |||
| 214 | - | OpaqueRect Rect | |||
| 215 | - | // Fade is how much wp_alpha_modifier_v1 fades the surface: 0 none, | |||
| 216 | - | // 1 invisible. Its opacity is multiplied by 1-Fade. | |||
| 217 | - | Fade float32 | |||
| 218 | - | Color SurfaceColor | |||
| 219 | - | SHM *SHMBuffer | |||
| 220 | - | DMABuf *DMABuf | |||
| 221 | - | // Solid is a single-pixel buffer (Width and Height are 1): the surface is | |||
| 222 | - | // one flat color. | |||
| 223 | - | Solid *SolidColor | |||
| 224 | - | // Children are the subsurfaces, bottom to top, flattened. | |||
| 225 | - | Children []Subsurface | |||
| 226 | - | // Geometry is in logical pixels from the surface origin; empty means | |||
| 227 | - | // the whole surface. | |||
| 228 | - | Geometry Rect | |||
| 229 | - | // Async asks for tearing presentation (wp_tearing_control_v1); outputs | |||
| 230 | - | // honour it only in direct scanout. | |||
| 231 | - | Async bool | |||
| 232 | - | // Acquire is the explicit-sync fence of this content's root buffer | |||
| 233 | - | // (wp_linux_drm_syncobj_v1): readers wait on it before reading the | |||
| 234 | - | // buffer, instead of the buffer's implicit fences. Wayland owns it and | |||
| 235 | - | // closes it when the buffer is released; a reader that keeps it past | |||
| 236 | - | // the call duplicates it under Acquire.SyscallConn. nil: implicit sync. | |||
| 237 | - | // Wayland closes it only after every output reported a later content of | |||
| 238 | - | // the window as seen, so a reader never gets it already closed. | |||
| 239 | - | Acquire *os.File | |||
| 240 | - | // DamageHistory is what changed in the root surface's buffer over the | |||
| 241 | - | // window's last contents, oldest first, ending with this one (Seq). | |||
| 242 | - | // A renderer holding an older content redraws the union of the | |||
| 243 | - | // entries after it, or everything when the history does not reach it. | |||
| 244 | - | DamageHistory []SeqDamage | |||
| 245 | - | } | |||
| 246 | - | ||||
| 247 | - | // BufferTransform is a wl_output.transform value: bit 0 rotates 90° | |||
| 248 | - | // counter-clockwise, bit 1 rotates 180°, bit 2 flips around the vertical | |||
| 249 | - | // axis first. | |||
| 250 | - | type BufferTransform uint8 | |||
| 251 | - | ||||
| 252 | - | // Rotated reports whether width and height swap between buffer and surface. | |||
| 253 | - | func (t BufferTransform) Rotated() bool { return t&1 != 0 } | |||
| 254 | - | ||||
| 255 | - | // Size returns the size of a w×h space seen through t: width and height swap | |||
| 256 | - | // when t is Rotated. | |||
| 257 | - | func (t BufferTransform) Size(w, h int) (int, int) { | |||
| 258 | - | if t.Rotated() { | |||
| 259 | - | return h, w | |||
| 260 | - | } | |||
| 261 | - | return w, h | |||
| 262 | - | } | |||
| 263 | - | ||||
| 264 | - | // ToBuffer maps a point of a w×h surface (logical, before scale) to the | |||
| 265 | - | // same point of its buffer, in the buffer's untransformed w'×h' axes, where | |||
| 266 | - | // w' and h' swap when the transform is Rotated. | |||
| 267 | - | func (t BufferTransform) ToBuffer(x, y, w, h float64) (float64, float64) { | |||
| 268 | - | if t&4 != 0 { | |||
| 269 | - | x = w - x | |||
| 270 | - | } | |||
| 271 | - | switch t & 3 { | |||
| 272 | - | case 1: | |||
| 273 | - | return y, w - x | |||
| 274 | - | case 2: | |||
| 275 | - | return w - x, h - y | |||
| 276 | - | case 3: | |||
| 277 | - | return h - y, x | |||
| 278 | - | } | |||
| 279 | - | return x, y | |||
| 280 | - | } | |||
| 281 | - | ||||
| 282 | - | // Invert is the transform that maps target points back: for a w×h scene and | |||
| 283 | - | // its w'×h' target, t.Invert().ToBuffer(t.ToBuffer(p, w, h), w', h') == p. | |||
| 284 | - | func (t BufferTransform) Invert() BufferTransform { | |||
| 285 | - | if t&1 != 0 && t&4 == 0 { | |||
| 286 | - | return t ^ 2 | |||
| 287 | - | } | |||
| 288 | - | return t | |||
| 289 | - | } | |||
| 290 | - | ||||
| 291 | - | // RectToBuffer maps a rectangle of a w×h surface to the buffer. Integer | |||
| 292 | - | // rectangles stay integer under these transforms; rounding absorbs float noise. | |||
| 293 | - | func (t BufferTransform) RectToBuffer(r image.Rectangle, w, h int) image.Rectangle { | |||
| 294 | - | ax, ay := t.ToBuffer(float64(r.Min.X), float64(r.Min.Y), float64(w), float64(h)) | |||
| 295 | - | bx, by := t.ToBuffer(float64(r.Max.X), float64(r.Max.Y), float64(w), float64(h)) | |||
| 296 | - | return image.Rect(int(math.Round(ax)), int(math.Round(ay)), int(math.Round(bx)), int(math.Round(by))) | |||
| 297 | - | } | |||
| 298 | - | ||||
| 299 | - | // SeqDamage is what content Seq changed from the one before: Rects in | |||
| 300 | - | // root buffer pixels, or everything when Full. | |||
| 301 | - | type SeqDamage struct { | |||
| 302 | - | Seq uint64 | |||
| 303 | - | Full bool | |||
| 304 | - | Rects []Rect | |||
| 305 | - | } | |||
| 306 | - | ||||
| 307 | - | // DamageSince is the union of changes after content seq up to this one, | |||
| 308 | - | // and false when the history does not reach back to seq (redraw all). | |||
| 309 | - | func (c SurfaceContent) DamageSince(seq uint64) ([]Rect, bool) { | |||
| 310 | - | out, ok := c.AppendDamageSince(nil, seq) | |||
| 311 | - | if !ok { | |||
| 312 | - | return nil, false | |||
| 313 | - | } | |||
| 314 | - | return out, true | |||
| 315 | - | } | |||
| 316 | - | ||||
| 317 | - | // AppendDamageSince is DamageSince appending to dst, so a caller reusing | |||
| 318 | - | // its slice allocates nothing. When the history does not reach back to seq | |||
| 319 | - | // it returns dst unchanged and false. | |||
| 320 | - | func (c SurfaceContent) AppendDamageSince(dst []Rect, seq uint64) ([]Rect, bool) { | |||
| 321 | - | if seq >= c.Seq { | |||
| 322 | - | return dst, true | |||
| 323 | - | } | |||
| 324 | - | h := c.DamageHistory | |||
| 325 | - | if len(h) == 0 || h[0].Seq > seq+1 || h[len(h)-1].Seq != c.Seq { | |||
| 326 | - | return dst, false | |||
| 327 | - | } | |||
| 328 | - | n := len(dst) | |||
| 329 | - | for _, d := range h { | |||
| 330 | - | if d.Seq <= seq { | |||
| 331 | - | continue | |||
| 332 | - | } | |||
| 333 | - | if d.Full { | |||
| 334 | - | return dst[:n], false | |||
| 335 | - | } | |||
| 336 | - | dst = append(dst, d.Rects...) | |||
| 337 | - | } | |||
| 338 | - | return dst, true | |||
| 339 | - | } | |||
| 340 | - | ||||
| 341 | - | // Subsurface is a child surface at X, Y logical pixels from the root | |||
| 342 | - | // surface origin. Below children are drawn under the root surface. | |||
| 343 | - | type Subsurface struct { | |||
| 344 | - | X, Y int | |||
| 345 | - | Below bool | |||
| 346 | - | SurfaceContent | |||
| 347 | - | } | |||
| 348 | - | ||||
| 349 | - | // HasBuffer reports whether the surface itself carries a client buffer. | |||
| 350 | - | func (c SurfaceContent) HasBuffer() bool { | |||
| 351 | - | return c.SHM != nil || c.DMABuf != nil || c.Solid != nil | |||
| 352 | - | } | |||
| 353 | - | ||||
| 354 | - | // SolidColor is the color of a single-pixel buffer: premultiplied by alpha, | |||
| 355 | - | // 0 to 1, sRGB-encoded like the pixels of client buffers. | |||
| 356 | - | type SolidColor struct{ R, G, B, A float32 } | |||
| 357 | - | ||||
| 358 | - | // Empty reports whether the content has nothing to draw. | |||
| 359 | - | func (c SurfaceContent) Empty() bool { | |||
| 360 | - | return !c.HasBuffer() && len(c.Children) == 0 | |||
| 361 | - | } | |||
| 362 | - | ||||
| 363 | - | // SHMBuffer is a client wl_shm buffer: B8G8R8A8 pixels (argb8888/xrgb8888 | |||
| 364 | - | // little-endian) at Offset in the pool File, Stride bytes per row. The | |||
| 365 | - | // client keeps writing the file, so renderers copy from it when they draw. | |||
| 366 | - | // Pool is unique per wl_shm pool for the session: renderers map a pool | |||
| 367 | - | // once. File stays open while the client's buffer exists and follows the | |||
| 368 | - | // DMABuf rules: duplicate it under File.SyscallConn before keeping it. A | |||
| 369 | - | // client may shrink the file at any time, so readers must survive faults. | |||
| 370 | - | type SHMBuffer struct { | |||
| 371 | - | Pool uint64 | |||
| 372 | - | File *os.File | |||
| 373 | - | Offset, Stride int | |||
| 374 | - | } | |||
| 375 | - | ||||
| 376 | - | // DMABuf is a client GPU buffer (linux-dmabuf). Its files stay open while | |||
| 377 | - | // the client's buffer exists; a renderer that keeps it must duplicate the | |||
| 378 | - | // descriptors under File.SyscallConn so a concurrent close cannot hand it a | |||
| 379 | - | // reused one. ID is unique per buffer for the whole session: renderers key | |||
| 380 | - | // their imports on it. | |||
| 381 | - | type DMABuf struct { | |||
| 382 | - | ID uint64 | |||
| 383 | - | Width, Height int | |||
| 384 | - | Format uint32 // DRM fourcc | |||
| 385 | - | Modifier uint64 | |||
| 386 | - | Planes []DMABufPlane | |||
| 387 | - | } | |||
| 388 | - | ||||
| 389 | - | // DMABufPlane is one plane of a DMABuf. | |||
| 390 | - | type DMABufPlane struct { | |||
| 391 | - | File *os.File | |||
| 392 | - | Offset, Stride uint32 | |||
| 393 | - | } | |||
| 394 | - | ||||
| 395 | - | // DMABufFormat is a DRM fourcc with a modifier the renderer can import. | |||
| 396 | - | type DMABufFormat struct { | |||
| 397 | - | Format uint32 | |||
| 398 | - | Modifier uint64 | |||
| 399 | - | } | |||
| 400 | - | ||||
| 401 | - | // DMABufSupport is what the renderer imports: the render device (a dev_t, | |||
| 402 | - | // 0 when unknown) and the formats. No formats means no dmabuf. | |||
| 403 | - | type DMABufSupport struct { | |||
| 404 | - | Device uint64 | |||
| 405 | - | Formats []DMABufFormat | |||
| 406 | - | } | |||
| 407 | - | ||||
| 408 | // SceneWindow carries core → renderer window placement. | 140 | // SceneWindow carries core → renderer window placement. | ||
| 409 | type SceneWindow struct { | 141 | type SceneWindow struct { | ||
| 410 | ID WindowID | 142 | ID WindowID | ||
A internal/ports/surface_content.go +270 -0
| @@ -0,0 +1,270 @@ | |||
| 1 | + | package ports | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "image" | |
| 5 | + | "math" | |
| 6 | + | "os" | |
| 7 | + | ) | |
| 8 | + | ||
| 9 | + | // SurfaceColor is the committed encoding of a surface. Zero means sRGB. | |
| 10 | + | // PQ+BT.2020 and extended-linear sRGB are color-managed; zero is legacy | |
| 11 | + | // sRGB. MaxCLL/MaxFALL are in nits. YUV metadata is per surface commit. | |
| 12 | + | type SurfaceColor struct { | |
| 13 | + | TF, Primaries uint8 | |
| 14 | + | MaxCLL, MaxFALL uint16 | |
| 15 | + | // Coefficients/range follow wp_color_representation_v1 (zero = | |
| 16 | + | // unspecified, for YUV interpreted as BT.709 limited). Chroma is a | |
| 17 | + | // H.273 Chroma420SampleLocType + 1 (zero defaults to type 0). | |
| 18 | + | Coefficients, Range, Chroma uint8 | |
| 19 | + | } | |
| 20 | + | ||
| 21 | + | const ( | |
| 22 | + | ColorTFPQ uint8 = 11 // wp_color_manager_v1 ST2084 PQ | |
| 23 | + | ColorTFExtendedLinear uint8 = 5 // wp_color_manager_v1 ext_linear | |
| 24 | + | ColorPrimariesBT2020 uint8 = 6 // wp_color_manager_v1 BT.2020 | |
| 25 | + | ColorPrimariesSRGB uint8 = 1 | |
| 26 | + | ColorTFSRGB uint8 = 9 // wp_color_manager_v1 sRGB | |
| 27 | + | ) | |
| 28 | + | ||
| 29 | + | func (c SurfaceColor) IsPQ2020() bool { | |
| 30 | + | return c.TF == ColorTFPQ && c.Primaries == ColorPrimariesBT2020 | |
| 31 | + | } | |
| 32 | + | ||
| 33 | + | func (c SurfaceColor) IsExtendedLinear() bool { | |
| 34 | + | return c.TF == ColorTFExtendedLinear && c.Primaries == ColorPrimariesSRGB | |
| 35 | + | } | |
| 36 | + | ||
| 37 | + | // ContentType is the committed wp_content_type_v1 hint. | |
| 38 | + | type ContentType uint8 | |
| 39 | + | ||
| 40 | + | const ( | |
| 41 | + | ContentNone ContentType = iota | |
| 42 | + | ContentPhoto | |
| 43 | + | ContentVideo | |
| 44 | + | ContentGame | |
| 45 | + | ) | |
| 46 | + | ||
| 47 | + | // SurfaceContent carries wayland → output the latest committed content of a | |
| 48 | + | // window: SHM, a client shared-memory buffer, DMABuf, a GPU buffer, or | |
| 49 | + | // Solid, a single-pixel buffer. Renderers read them in place and never modify | |
| 50 | + | // them. Empty means the window | |
| 51 | + | // has no content. LogicalW and LogicalH are the surface size in logical | |
| 52 | + | // pixels (buffer scale and viewport applied). Subsurfaces come in Children; | |
| 53 | + | // Geometry is the part of the surface that is the window (xdg window | |
| 54 | + | // geometry), the rest being client shadows. | |
| 55 | + | type SurfaceContent struct { | |
| 56 | + | ID WindowID | |
| 57 | + | ContentType ContentType | |
| 58 | + | // Surface is a never-reused wl_surface identity; Version identifies its | |
| 59 | + | // applied buffer content independently of the window publication Seq. | |
| 60 | + | Surface, Version uint64 | |
| 61 | + | // Seq counts the window's contents (wayland sets it), for release. | |
| 62 | + | Seq uint64 | |
| 63 | + | Width, Height int | |
| 64 | + | LogicalW, LogicalH int | |
| 65 | + | // Source is the committed crop in buffer pixels: x, y, width, height. | |
| 66 | + | // Zero width means the entire buffer is used. | |
| 67 | + | Source [4]float32 | |
| 68 | + | // Transform is the wl_output.transform the client applied to the | |
| 69 | + | // buffer: readers apply its inverse. Width and Height stay the buffer's. | |
| 70 | + | Transform BufferTransform | |
| 71 | + | // Opaque ignores the alpha byte: an x format, or an opaque region that | |
| 72 | + | // covers the whole surface. It is false while Fade is set. | |
| 73 | + | Opaque bool | |
| 74 | + | // OpaqueRect is the largest rect of the opaque region inside a surface | |
| 75 | + | // that is not wholly Opaque, in logical pixels from the surface origin: | |
| 76 | + | // the pixels there ignore alpha too, so the surfaces below it are hidden | |
| 77 | + | // by it. Empty when there is none or while Fade is set. | |
| 78 | + | OpaqueRect Rect | |
| 79 | + | // Fade is how much wp_alpha_modifier_v1 fades the surface: 0 none, | |
| 80 | + | // 1 invisible. Its opacity is multiplied by 1-Fade. | |
| 81 | + | Fade float32 | |
| 82 | + | Color SurfaceColor | |
| 83 | + | SHM *SHMBuffer | |
| 84 | + | DMABuf *DMABuf | |
| 85 | + | // Solid is a single-pixel buffer (Width and Height are 1): the surface is | |
| 86 | + | // one flat color. | |
| 87 | + | Solid *SolidColor | |
| 88 | + | // Children are the subsurfaces, bottom to top, flattened. | |
| 89 | + | Children []Subsurface | |
| 90 | + | // Geometry is in logical pixels from the surface origin; empty means | |
| 91 | + | // the whole surface. | |
| 92 | + | Geometry Rect | |
| 93 | + | // Async asks for tearing presentation (wp_tearing_control_v1); outputs | |
| 94 | + | // honour it only in direct scanout. | |
| 95 | + | Async bool | |
| 96 | + | // Acquire is the explicit-sync fence of this content's root buffer | |
| 97 | + | // (wp_linux_drm_syncobj_v1): readers wait on it before reading the | |
| 98 | + | // buffer, instead of the buffer's implicit fences. Wayland owns it and | |
| 99 | + | // closes it when the buffer is released; a reader that keeps it past | |
| 100 | + | // the call duplicates it under Acquire.SyscallConn. nil: implicit sync. | |
| 101 | + | // Wayland closes it only after every output reported a later content of | |
| 102 | + | // the window as seen, so a reader never gets it already closed. | |
| 103 | + | Acquire *os.File | |
| 104 | + | // DamageHistory is what changed in the root surface's buffer over the | |
| 105 | + | // window's last contents, oldest first, ending with this one (Seq). | |
| 106 | + | // A renderer holding an older content redraws the union of the | |
| 107 | + | // entries after it, or everything when the history does not reach it. | |
| 108 | + | DamageHistory []SeqDamage | |
| 109 | + | } | |
| 110 | + | ||
| 111 | + | // BufferTransform is a wl_output.transform value: bit 0 rotates 90° | |
| 112 | + | // counter-clockwise, bit 1 rotates 180°, bit 2 flips around the vertical | |
| 113 | + | // axis first. | |
| 114 | + | type BufferTransform uint8 | |
| 115 | + | ||
| 116 | + | // Rotated reports whether width and height swap between buffer and surface. | |
| 117 | + | func (t BufferTransform) Rotated() bool { return t&1 != 0 } | |
| 118 | + | ||
| 119 | + | // Size returns the size of a w×h space seen through t: width and height swap | |
| 120 | + | // when t is Rotated. | |
| 121 | + | func (t BufferTransform) Size(w, h int) (int, int) { | |
| 122 | + | if t.Rotated() { | |
| 123 | + | return h, w | |
| 124 | + | } | |
| 125 | + | return w, h | |
| 126 | + | } | |
| 127 | + | ||
| 128 | + | // ToBuffer maps a point of a w×h surface (logical, before scale) to the | |
| 129 | + | // same point of its buffer, in the buffer's untransformed w'×h' axes, where | |
| 130 | + | // w' and h' swap when the transform is Rotated. | |
| 131 | + | func (t BufferTransform) ToBuffer(x, y, w, h float64) (float64, float64) { | |
| 132 | + | if t&4 != 0 { | |
| 133 | + | x = w - x | |
| 134 | + | } | |
| 135 | + | switch t & 3 { | |
| 136 | + | case 1: | |
| 137 | + | return y, w - x | |
| 138 | + | case 2: | |
| 139 | + | return w - x, h - y | |
| 140 | + | case 3: | |
| 141 | + | return h - y, x | |
| 142 | + | } | |
| 143 | + | return x, y | |
| 144 | + | } | |
| 145 | + | ||
| 146 | + | // Invert is the transform that maps target points back: for a w×h scene and | |
| 147 | + | // its w'×h' target, t.Invert().ToBuffer(t.ToBuffer(p, w, h), w', h') == p. | |
| 148 | + | func (t BufferTransform) Invert() BufferTransform { | |
| 149 | + | if t&1 != 0 && t&4 == 0 { | |
| 150 | + | return t ^ 2 | |
| 151 | + | } | |
| 152 | + | return t | |
| 153 | + | } | |
| 154 | + | ||
| 155 | + | // RectToBuffer maps a rectangle of a w×h surface to the buffer. Integer | |
| 156 | + | // rectangles stay integer under these transforms; rounding absorbs float noise. | |
| 157 | + | func (t BufferTransform) RectToBuffer(r image.Rectangle, w, h int) image.Rectangle { | |
| 158 | + | ax, ay := t.ToBuffer(float64(r.Min.X), float64(r.Min.Y), float64(w), float64(h)) | |
| 159 | + | bx, by := t.ToBuffer(float64(r.Max.X), float64(r.Max.Y), float64(w), float64(h)) | |
| 160 | + | return image.Rect(int(math.Round(ax)), int(math.Round(ay)), int(math.Round(bx)), int(math.Round(by))) | |
| 161 | + | } | |
| 162 | + | ||
| 163 | + | // SeqDamage is what content Seq changed from the one before: Rects in | |
| 164 | + | // root buffer pixels, or everything when Full. | |
| 165 | + | type SeqDamage struct { | |
| 166 | + | Seq uint64 | |
| 167 | + | Full bool | |
| 168 | + | Rects []Rect | |
| 169 | + | } | |
| 170 | + | ||
| 171 | + | // DamageSince is the union of changes after content seq up to this one, | |
| 172 | + | // and false when the history does not reach back to seq (redraw all). | |
| 173 | + | func (c SurfaceContent) DamageSince(seq uint64) ([]Rect, bool) { | |
| 174 | + | out, ok := c.AppendDamageSince(nil, seq) | |
| 175 | + | if !ok { | |
| 176 | + | return nil, false | |
| 177 | + | } | |
| 178 | + | return out, true | |
| 179 | + | } | |
| 180 | + | ||
| 181 | + | // AppendDamageSince is DamageSince appending to dst, so a caller reusing | |
| 182 | + | // its slice allocates nothing. When the history does not reach back to seq | |
| 183 | + | // it returns dst unchanged and false. | |
| 184 | + | func (c SurfaceContent) AppendDamageSince(dst []Rect, seq uint64) ([]Rect, bool) { | |
| 185 | + | if seq >= c.Seq { | |
| 186 | + | return dst, true | |
| 187 | + | } | |
| 188 | + | h := c.DamageHistory | |
| 189 | + | if len(h) == 0 || h[0].Seq > seq+1 || h[len(h)-1].Seq != c.Seq { | |
| 190 | + | return dst, false | |
| 191 | + | } | |
| 192 | + | n := len(dst) | |
| 193 | + | for _, d := range h { | |
| 194 | + | if d.Seq <= seq { | |
| 195 | + | continue | |
| 196 | + | } | |
| 197 | + | if d.Full { | |
| 198 | + | return dst[:n], false | |
| 199 | + | } | |
| 200 | + | dst = append(dst, d.Rects...) | |
| 201 | + | } | |
| 202 | + | return dst, true | |
| 203 | + | } | |
| 204 | + | ||
| 205 | + | // Subsurface is a child surface at X, Y logical pixels from the root | |
| 206 | + | // surface origin. Below children are drawn under the root surface. | |
| 207 | + | type Subsurface struct { | |
| 208 | + | X, Y int | |
| 209 | + | Below bool | |
| 210 | + | SurfaceContent | |
| 211 | + | } | |
| 212 | + | ||
| 213 | + | // HasBuffer reports whether the surface itself carries a client buffer. | |
| 214 | + | func (c SurfaceContent) HasBuffer() bool { | |
| 215 | + | return c.SHM != nil || c.DMABuf != nil || c.Solid != nil | |
| 216 | + | } | |
| 217 | + | ||
| 218 | + | // SolidColor is the color of a single-pixel buffer: premultiplied by alpha, | |
| 219 | + | // 0 to 1, sRGB-encoded like the pixels of client buffers. | |
| 220 | + | type SolidColor struct{ R, G, B, A float32 } | |
| 221 | + | ||
| 222 | + | // Empty reports whether the content has nothing to draw. | |
| 223 | + | func (c SurfaceContent) Empty() bool { | |
| 224 | + | return !c.HasBuffer() && len(c.Children) == 0 | |
| 225 | + | } | |
| 226 | + | ||
| 227 | + | // SHMBuffer is a client wl_shm buffer: B8G8R8A8 pixels (argb8888/xrgb8888 | |
| 228 | + | // little-endian) at Offset in the pool File, Stride bytes per row. The | |
| 229 | + | // client keeps writing the file, so renderers copy from it when they draw. | |
| 230 | + | // Pool is unique per wl_shm pool for the session: renderers map a pool | |
| 231 | + | // once. File stays open while the client's buffer exists and follows the | |
| 232 | + | // DMABuf rules: duplicate it under File.SyscallConn before keeping it. A | |
| 233 | + | // client may shrink the file at any time, so readers must survive faults. | |
| 234 | + | type SHMBuffer struct { | |
| 235 | + | Pool uint64 | |
| 236 | + | File *os.File | |
| 237 | + | Offset, Stride int | |
| 238 | + | } | |
| 239 | + | ||
| 240 | + | // DMABuf is a client GPU buffer (linux-dmabuf). Its files stay open while | |
| 241 | + | // the client's buffer exists; a renderer that keeps it must duplicate the | |
| 242 | + | // descriptors under File.SyscallConn so a concurrent close cannot hand it a | |
| 243 | + | // reused one. ID is unique per buffer for the whole session: renderers key | |
| 244 | + | // their imports on it. | |
| 245 | + | type DMABuf struct { | |
| 246 | + | ID uint64 | |
| 247 | + | Width, Height int | |
| 248 | + | Format uint32 // DRM fourcc | |
| 249 | + | Modifier uint64 | |
| 250 | + | Planes []DMABufPlane | |
| 251 | + | } | |
| 252 | + | ||
| 253 | + | // DMABufPlane is one plane of a DMABuf. | |
| 254 | + | type DMABufPlane struct { | |
| 255 | + | File *os.File | |
| 256 | + | Offset, Stride uint32 | |
| 257 | + | } | |
| 258 | + | ||
| 259 | + | // DMABufFormat is a DRM fourcc with a modifier the renderer can import. | |
| 260 | + | type DMABufFormat struct { | |
| 261 | + | Format uint32 | |
| 262 | + | Modifier uint64 | |
| 263 | + | } | |
| 264 | + | ||
| 265 | + | // DMABufSupport is what the renderer imports: the render device (a dev_t, | |
| 266 | + | // 0 when unknown) and the formats. No formats means no dmabuf. | |
| 267 | + | type DMABufSupport struct { | |
| 268 | + | Device uint64 | |
| 269 | + | Formats []DMABufFormat | |
| 270 | + | } | |
| @@ -0,0 +1,270 @@ | |||||
| 1 | + | package ports | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "image" | |||
| 5 | + | "math" | |||
| 6 | + | "os" | |||
| 7 | + | ) | |||
| 8 | + | ||||
| 9 | + | // SurfaceColor is the committed encoding of a surface. Zero means sRGB. | |||
| 10 | + | // PQ+BT.2020 and extended-linear sRGB are color-managed; zero is legacy | |||
| 11 | + | // sRGB. MaxCLL/MaxFALL are in nits. YUV metadata is per surface commit. | |||
| 12 | + | type SurfaceColor struct { | |||
| 13 | + | TF, Primaries uint8 | |||
| 14 | + | MaxCLL, MaxFALL uint16 | |||
| 15 | + | // Coefficients/range follow wp_color_representation_v1 (zero = | |||
| 16 | + | // unspecified, for YUV interpreted as BT.709 limited). Chroma is a | |||
| 17 | + | // H.273 Chroma420SampleLocType + 1 (zero defaults to type 0). | |||
| 18 | + | Coefficients, Range, Chroma uint8 | |||
| 19 | + | } | |||
| 20 | + | ||||
| 21 | + | const ( | |||
| 22 | + | ColorTFPQ uint8 = 11 // wp_color_manager_v1 ST2084 PQ | |||
| 23 | + | ColorTFExtendedLinear uint8 = 5 // wp_color_manager_v1 ext_linear | |||
| 24 | + | ColorPrimariesBT2020 uint8 = 6 // wp_color_manager_v1 BT.2020 | |||
| 25 | + | ColorPrimariesSRGB uint8 = 1 | |||
| 26 | + | ColorTFSRGB uint8 = 9 // wp_color_manager_v1 sRGB | |||
| 27 | + | ) | |||
| 28 | + | ||||
| 29 | + | func (c SurfaceColor) IsPQ2020() bool { | |||
| 30 | + | return c.TF == ColorTFPQ && c.Primaries == ColorPrimariesBT2020 | |||
| 31 | + | } | |||
| 32 | + | ||||
| 33 | + | func (c SurfaceColor) IsExtendedLinear() bool { | |||
| 34 | + | return c.TF == ColorTFExtendedLinear && c.Primaries == ColorPrimariesSRGB | |||
| 35 | + | } | |||
| 36 | + | ||||
| 37 | + | // ContentType is the committed wp_content_type_v1 hint. | |||
| 38 | + | type ContentType uint8 | |||
| 39 | + | ||||
| 40 | + | const ( | |||
| 41 | + | ContentNone ContentType = iota | |||
| 42 | + | ContentPhoto | |||
| 43 | + | ContentVideo | |||
| 44 | + | ContentGame | |||
| 45 | + | ) | |||
| 46 | + | ||||
| 47 | + | // SurfaceContent carries wayland → output the latest committed content of a | |||
| 48 | + | // window: SHM, a client shared-memory buffer, DMABuf, a GPU buffer, or | |||
| 49 | + | // Solid, a single-pixel buffer. Renderers read them in place and never modify | |||
| 50 | + | // them. Empty means the window | |||
| 51 | + | // has no content. LogicalW and LogicalH are the surface size in logical | |||
| 52 | + | // pixels (buffer scale and viewport applied). Subsurfaces come in Children; | |||
| 53 | + | // Geometry is the part of the surface that is the window (xdg window | |||
| 54 | + | // geometry), the rest being client shadows. | |||
| 55 | + | type SurfaceContent struct { | |||
| 56 | + | ID WindowID | |||
| 57 | + | ContentType ContentType | |||
| 58 | + | // Surface is a never-reused wl_surface identity; Version identifies its | |||
| 59 | + | // applied buffer content independently of the window publication Seq. | |||
| 60 | + | Surface, Version uint64 | |||
| 61 | + | // Seq counts the window's contents (wayland sets it), for release. | |||
| 62 | + | Seq uint64 | |||
| 63 | + | Width, Height int | |||
| 64 | + | LogicalW, LogicalH int | |||
| 65 | + | // Source is the committed crop in buffer pixels: x, y, width, height. | |||
| 66 | + | // Zero width means the entire buffer is used. | |||
| 67 | + | Source [4]float32 | |||
| 68 | + | // Transform is the wl_output.transform the client applied to the | |||
| 69 | + | // buffer: readers apply its inverse. Width and Height stay the buffer's. | |||
| 70 | + | Transform BufferTransform | |||
| 71 | + | // Opaque ignores the alpha byte: an x format, or an opaque region that | |||
| 72 | + | // covers the whole surface. It is false while Fade is set. | |||
| 73 | + | Opaque bool | |||
| 74 | + | // OpaqueRect is the largest rect of the opaque region inside a surface | |||
| 75 | + | // that is not wholly Opaque, in logical pixels from the surface origin: | |||
| 76 | + | // the pixels there ignore alpha too, so the surfaces below it are hidden | |||
| 77 | + | // by it. Empty when there is none or while Fade is set. | |||
| 78 | + | OpaqueRect Rect | |||
| 79 | + | // Fade is how much wp_alpha_modifier_v1 fades the surface: 0 none, | |||
| 80 | + | // 1 invisible. Its opacity is multiplied by 1-Fade. | |||
| 81 | + | Fade float32 | |||
| 82 | + | Color SurfaceColor | |||
| 83 | + | SHM *SHMBuffer | |||
| 84 | + | DMABuf *DMABuf | |||
| 85 | + | // Solid is a single-pixel buffer (Width and Height are 1): the surface is | |||
| 86 | + | // one flat color. | |||
| 87 | + | Solid *SolidColor | |||
| 88 | + | // Children are the subsurfaces, bottom to top, flattened. | |||
| 89 | + | Children []Subsurface | |||
| 90 | + | // Geometry is in logical pixels from the surface origin; empty means | |||
| 91 | + | // the whole surface. | |||
| 92 | + | Geometry Rect | |||
| 93 | + | // Async asks for tearing presentation (wp_tearing_control_v1); outputs | |||
| 94 | + | // honour it only in direct scanout. | |||
| 95 | + | Async bool | |||
| 96 | + | // Acquire is the explicit-sync fence of this content's root buffer | |||
| 97 | + | // (wp_linux_drm_syncobj_v1): readers wait on it before reading the | |||
| 98 | + | // buffer, instead of the buffer's implicit fences. Wayland owns it and | |||
| 99 | + | // closes it when the buffer is released; a reader that keeps it past | |||
| 100 | + | // the call duplicates it under Acquire.SyscallConn. nil: implicit sync. | |||
| 101 | + | // Wayland closes it only after every output reported a later content of | |||
| 102 | + | // the window as seen, so a reader never gets it already closed. | |||
| 103 | + | Acquire *os.File | |||
| 104 | + | // DamageHistory is what changed in the root surface's buffer over the | |||
| 105 | + | // window's last contents, oldest first, ending with this one (Seq). | |||
| 106 | + | // A renderer holding an older content redraws the union of the | |||
| 107 | + | // entries after it, or everything when the history does not reach it. | |||
| 108 | + | DamageHistory []SeqDamage | |||
| 109 | + | } | |||
| 110 | + | ||||
| 111 | + | // BufferTransform is a wl_output.transform value: bit 0 rotates 90° | |||
| 112 | + | // counter-clockwise, bit 1 rotates 180°, bit 2 flips around the vertical | |||
| 113 | + | // axis first. | |||
| 114 | + | type BufferTransform uint8 | |||
| 115 | + | ||||
| 116 | + | // Rotated reports whether width and height swap between buffer and surface. | |||
| 117 | + | func (t BufferTransform) Rotated() bool { return t&1 != 0 } | |||
| 118 | + | ||||
| 119 | + | // Size returns the size of a w×h space seen through t: width and height swap | |||
| 120 | + | // when t is Rotated. | |||
| 121 | + | func (t BufferTransform) Size(w, h int) (int, int) { | |||
| 122 | + | if t.Rotated() { | |||
| 123 | + | return h, w | |||
| 124 | + | } | |||
| 125 | + | return w, h | |||
| 126 | + | } | |||
| 127 | + | ||||
| 128 | + | // ToBuffer maps a point of a w×h surface (logical, before scale) to the | |||
| 129 | + | // same point of its buffer, in the buffer's untransformed w'×h' axes, where | |||
| 130 | + | // w' and h' swap when the transform is Rotated. | |||
| 131 | + | func (t BufferTransform) ToBuffer(x, y, w, h float64) (float64, float64) { | |||
| 132 | + | if t&4 != 0 { | |||
| 133 | + | x = w - x | |||
| 134 | + | } | |||
| 135 | + | switch t & 3 { | |||
| 136 | + | case 1: | |||
| 137 | + | return y, w - x | |||
| 138 | + | case 2: | |||
| 139 | + | return w - x, h - y | |||
| 140 | + | case 3: | |||
| 141 | + | return h - y, x | |||
| 142 | + | } | |||
| 143 | + | return x, y | |||
| 144 | + | } | |||
| 145 | + | ||||
| 146 | + | // Invert is the transform that maps target points back: for a w×h scene and | |||
| 147 | + | // its w'×h' target, t.Invert().ToBuffer(t.ToBuffer(p, w, h), w', h') == p. | |||
| 148 | + | func (t BufferTransform) Invert() BufferTransform { | |||
| 149 | + | if t&1 != 0 && t&4 == 0 { | |||
| 150 | + | return t ^ 2 | |||
| 151 | + | } | |||
| 152 | + | return t | |||
| 153 | + | } | |||
| 154 | + | ||||
| 155 | + | // RectToBuffer maps a rectangle of a w×h surface to the buffer. Integer | |||
| 156 | + | // rectangles stay integer under these transforms; rounding absorbs float noise. | |||
| 157 | + | func (t BufferTransform) RectToBuffer(r image.Rectangle, w, h int) image.Rectangle { | |||
| 158 | + | ax, ay := t.ToBuffer(float64(r.Min.X), float64(r.Min.Y), float64(w), float64(h)) | |||
| 159 | + | bx, by := t.ToBuffer(float64(r.Max.X), float64(r.Max.Y), float64(w), float64(h)) | |||
| 160 | + | return image.Rect(int(math.Round(ax)), int(math.Round(ay)), int(math.Round(bx)), int(math.Round(by))) | |||
| 161 | + | } | |||
| 162 | + | ||||
| 163 | + | // SeqDamage is what content Seq changed from the one before: Rects in | |||
| 164 | + | // root buffer pixels, or everything when Full. | |||
| 165 | + | type SeqDamage struct { | |||
| 166 | + | Seq uint64 | |||
| 167 | + | Full bool | |||
| 168 | + | Rects []Rect | |||
| 169 | + | } | |||
| 170 | + | ||||
| 171 | + | // DamageSince is the union of changes after content seq up to this one, | |||
| 172 | + | // and false when the history does not reach back to seq (redraw all). | |||
| 173 | + | func (c SurfaceContent) DamageSince(seq uint64) ([]Rect, bool) { | |||
| 174 | + | out, ok := c.AppendDamageSince(nil, seq) | |||
| 175 | + | if !ok { | |||
| 176 | + | return nil, false | |||
| 177 | + | } | |||
| 178 | + | return out, true | |||
| 179 | + | } | |||
| 180 | + | ||||
| 181 | + | // AppendDamageSince is DamageSince appending to dst, so a caller reusing | |||
| 182 | + | // its slice allocates nothing. When the history does not reach back to seq | |||
| 183 | + | // it returns dst unchanged and false. | |||
| 184 | + | func (c SurfaceContent) AppendDamageSince(dst []Rect, seq uint64) ([]Rect, bool) { | |||
| 185 | + | if seq >= c.Seq { | |||
| 186 | + | return dst, true | |||
| 187 | + | } | |||
| 188 | + | h := c.DamageHistory | |||
| 189 | + | if len(h) == 0 || h[0].Seq > seq+1 || h[len(h)-1].Seq != c.Seq { | |||
| 190 | + | return dst, false | |||
| 191 | + | } | |||
| 192 | + | n := len(dst) | |||
| 193 | + | for _, d := range h { | |||
| 194 | + | if d.Seq <= seq { | |||
| 195 | + | continue | |||
| 196 | + | } | |||
| 197 | + | if d.Full { | |||
| 198 | + | return dst[:n], false | |||
| 199 | + | } | |||
| 200 | + | dst = append(dst, d.Rects...) | |||
| 201 | + | } | |||
| 202 | + | return dst, true | |||
| 203 | + | } | |||
| 204 | + | ||||
| 205 | + | // Subsurface is a child surface at X, Y logical pixels from the root | |||
| 206 | + | // surface origin. Below children are drawn under the root surface. | |||
| 207 | + | type Subsurface struct { | |||
| 208 | + | X, Y int | |||
| 209 | + | Below bool | |||
| 210 | + | SurfaceContent | |||
| 211 | + | } | |||
| 212 | + | ||||
| 213 | + | // HasBuffer reports whether the surface itself carries a client buffer. | |||
| 214 | + | func (c SurfaceContent) HasBuffer() bool { | |||
| 215 | + | return c.SHM != nil || c.DMABuf != nil || c.Solid != nil | |||
| 216 | + | } | |||
| 217 | + | ||||
| 218 | + | // SolidColor is the color of a single-pixel buffer: premultiplied by alpha, | |||
| 219 | + | // 0 to 1, sRGB-encoded like the pixels of client buffers. | |||
| 220 | + | type SolidColor struct{ R, G, B, A float32 } | |||
| 221 | + | ||||
| 222 | + | // Empty reports whether the content has nothing to draw. | |||
| 223 | + | func (c SurfaceContent) Empty() bool { | |||
| 224 | + | return !c.HasBuffer() && len(c.Children) == 0 | |||
| 225 | + | } | |||
| 226 | + | ||||
| 227 | + | // SHMBuffer is a client wl_shm buffer: B8G8R8A8 pixels (argb8888/xrgb8888 | |||
| 228 | + | // little-endian) at Offset in the pool File, Stride bytes per row. The | |||
| 229 | + | // client keeps writing the file, so renderers copy from it when they draw. | |||
| 230 | + | // Pool is unique per wl_shm pool for the session: renderers map a pool | |||
| 231 | + | // once. File stays open while the client's buffer exists and follows the | |||
| 232 | + | // DMABuf rules: duplicate it under File.SyscallConn before keeping it. A | |||
| 233 | + | // client may shrink the file at any time, so readers must survive faults. | |||
| 234 | + | type SHMBuffer struct { | |||
| 235 | + | Pool uint64 | |||
| 236 | + | File *os.File | |||
| 237 | + | Offset, Stride int | |||
| 238 | + | } | |||
| 239 | + | ||||
| 240 | + | // DMABuf is a client GPU buffer (linux-dmabuf). Its files stay open while | |||
| 241 | + | // the client's buffer exists; a renderer that keeps it must duplicate the | |||
| 242 | + | // descriptors under File.SyscallConn so a concurrent close cannot hand it a | |||
| 243 | + | // reused one. ID is unique per buffer for the whole session: renderers key | |||
| 244 | + | // their imports on it. | |||
| 245 | + | type DMABuf struct { | |||
| 246 | + | ID uint64 | |||
| 247 | + | Width, Height int | |||
| 248 | + | Format uint32 // DRM fourcc | |||
| 249 | + | Modifier uint64 | |||
| 250 | + | Planes []DMABufPlane | |||
| 251 | + | } | |||
| 252 | + | ||||
| 253 | + | // DMABufPlane is one plane of a DMABuf. | |||
| 254 | + | type DMABufPlane struct { | |||
| 255 | + | File *os.File | |||
| 256 | + | Offset, Stride uint32 | |||
| 257 | + | } | |||
| 258 | + | ||||
| 259 | + | // DMABufFormat is a DRM fourcc with a modifier the renderer can import. | |||
| 260 | + | type DMABufFormat struct { | |||
| 261 | + | Format uint32 | |||
| 262 | + | Modifier uint64 | |||
| 263 | + | } | |||
| 264 | + | ||||
| 265 | + | // DMABufSupport is what the renderer imports: the render device (a dev_t, | |||
| 266 | + | // 0 when unknown) and the formats. No formats means no dmabuf. | |||
| 267 | + | type DMABufSupport struct { | |||
| 268 | + | Device uint64 | |||
| 269 | + | Formats []DMABufFormat | |||
| 270 | + | } | |||
A internal/ports/workspace_events.go +25 -0
| @@ -0,0 +1,25 @@ | |||
| 1 | + | package ports | |
| 2 | + | ||
| 3 | + | // WorkspaceActivate requests showing workspaces by stable ID, in commit order. | |
| 4 | + | // Core applies the entire batch before publishing a new state. | |
| 5 | + | type WorkspaceActivate struct{ IDs []uint64 } | |
| 6 | + | ||
| 7 | + | func (WorkspaceActivate) clientEvent() {} | |
| 8 | + | ||
| 9 | + | // Workspaces is an immutable, latest-only inventory of output workspaces. | |
| 10 | + | type Workspaces struct{ Outputs []WorkspaceOutput } | |
| 11 | + | type WorkspaceOutput struct { | |
| 12 | + | Name string | |
| 13 | + | Workspaces []WorkspaceInfo | |
| 14 | + | } | |
| 15 | + | type WorkspaceInfo struct { | |
| 16 | + | ID uint64 | |
| 17 | + | Configured string // configured name; empty for dynamic workspaces. ID, not this, identifies the workspace | |
| 18 | + | Name string | |
| 19 | + | Index int // zero-based vertical coordinate | |
| 20 | + | Active, Hidden bool | |
| 21 | + | // Frame is the workspace viewport in output-local logical pixels | |
| 22 | + | // (Monitor.Frame geometry: the whole output unless the workspace has a | |
| 23 | + | // size override). It is where a capture session of it lies. | |
| 24 | + | Frame Rect | |
| 25 | + | } | |
| @@ -0,0 +1,25 @@ | |||||
| 1 | + | package ports | |||
| 2 | + | ||||
| 3 | + | // WorkspaceActivate requests showing workspaces by stable ID, in commit order. | |||
| 4 | + | // Core applies the entire batch before publishing a new state. | |||
| 5 | + | type WorkspaceActivate struct{ IDs []uint64 } | |||
| 6 | + | ||||
| 7 | + | func (WorkspaceActivate) clientEvent() {} | |||
| 8 | + | ||||
| 9 | + | // Workspaces is an immutable, latest-only inventory of output workspaces. | |||
| 10 | + | type Workspaces struct{ Outputs []WorkspaceOutput } | |||
| 11 | + | type WorkspaceOutput struct { | |||
| 12 | + | Name string | |||
| 13 | + | Workspaces []WorkspaceInfo | |||
| 14 | + | } | |||
| 15 | + | type WorkspaceInfo struct { | |||
| 16 | + | ID uint64 | |||
| 17 | + | Configured string // configured name; empty for dynamic workspaces. ID, not this, identifies the workspace | |||
| 18 | + | Name string | |||
| 19 | + | Index int // zero-based vertical coordinate | |||
| 20 | + | Active, Hidden bool | |||
| 21 | + | // Frame is the workspace viewport in output-local logical pixels | |||
| 22 | + | // (Monitor.Frame geometry: the whole output unless the workspace has a | |||
| 23 | + | // size override). It is where a capture session of it lies. | |||
| 24 | + | Frame Rect | |||
| 25 | + | } | |||