surface.go RawHistoryFolder

1package wayland
2
3import (
4	"image"
5	"math"
6	"os"
7	"slices"
8	"time"
9
10	"github.com/bnema/go-wayland-bindings/server/presentationtime"
11
12	"github.com/bnema/go-wayland-bindings/server/wayland"
13	"github.com/bnema/go-wayland-bindings/server/wlrlayershell"
14	"github.com/bnema/go-wayland-bindings/server/xdgshell"
15	"github.com/bnema/neferwl/internal/ports"
16)
17
18type roleKind uint8
19
20const (
21	roleNone roleKind = iota
22	roleXDG
23	roleSubsurface
24	roleLayer
25	roleCursor
26	roleInputPopup
27	roleSessionLock
28)
29
30type surface struct {
31	wl                *wayland.Surface
32	viewport          *viewport
33	committedViewport viewportState // value snapshot; no allocation on viewport commits
34	// next is the state requested since the last commit (see pendingCommit).
35	next pendingCommit
36	// bufferScale, transform and opaque are committed state. formatOpaque is
37	// the current buffer's own opacity (x formats).
38	bufferScale  int
39	transform    ports.BufferTransform
40	opaque       []ports.Rect
41	formatOpaque bool
42	kind         roleKind
43	xdg          *xdgSurface
44	layer        *layerSurface
45	lock         *lockSurface
46	// bufferCommitted is permanent, including commits later discarded by FIFO.
47	bufferCommitted bool
48	server          *Server
49	current         *wayland.Buffer
50	lastW, lastH    int // last logged buffer size
51	role            func(bool)
52	destroyed       bool
53	// hotX, hotY is the click point of a cursor surface (logical).
54	hotX, hotY int
55	// on is the output the surface entered; scale is the last scale sent.
56	on    *output
57	scale float64
58	// prefTransform is the last preferred buffer transform sent (wl_surface
59	// v6+); zero (normal) is the protocol default.
60	prefTransform ports.BufferTransform
61	// content is the last content built from this surface's own buffer;
62	// has is false while no buffer is attached.
63	inputAll                bool
64	inputRects              []ports.Rect
65	sentInput, lastInputAll bool
66	lastInputRects          []ports.Rect
67	content                 ports.SurfaceContent
68	has                     bool
69	identity                uint64
70	version                 uint64
71	// cachedTree is never modified once returned to emitContent.
72	cachedTree                 []ports.Subsurface
73	treeDirty                  bool
74	commitFresh, commitSkipped bool
75	queuedScale                int
76	queuedBuffer               uint32
77	sub                        subState
78	// tearing is the surface's wp_tearing_control_v1; async the committed
79	// hint.
80	tearing               *tearingHandler
81	async                 bool
82	colorControl          *colorSurface
83	color                 SurfaceColor
84	representation        surfaceRepresentation
85	representationControl *representationSurface
86	// Presentation constraints (fifo.go): the barrier, and the commits
87	// waiting to apply.
88	fifo      *fifoHandler
89	timer     *timerHandler
90	barrier   bool
91	barrierAt time.Time
92	queue     []*update
93	// contentType is the wp_content_type_v1; contentKind the committed type.
94	contentType *contentTypeHandler
95	// alpha is the surface's wp_alpha_modifier_surface_v1.
96	alpha       *alphaHandler
97	contentKind uint32
98	// committed is what the last commit changed, in buffer pixels (full:
99	// everything), read by the window's damage history.
100	committed damage
101	// Explicit sync (syncobj.go): the surface's syncobj object and the
102	// current buffer's hold.
103	sync *syncState
104	hold syncHold
105}
106
107// pendingCommit is the double-buffered wl_surface state requested since the
108// last commit. A commit captures it whole into an update (takePending):
109// one-shot parts are cleared, sticky parts (scale, transform, hints, color)
110// carry over to the next commit.
111type pendingCommit struct {
112	// buffer is attached (attached reports an attach request, nil detaches).
113	buffer   *wayland.Buffer
114	attached bool
115	// scale is set by set_buffer_scale, transform by set_buffer_transform.
116	scale     int
117	transform ports.BufferTransform
118	// opaque and the input region are set by their requests (the Set flags).
119	opaque             []ports.Rect
120	opaqueSet          bool
121	inputAll, inputSet bool
122	inputRects         []ports.Rect
123	callbacks          []*wayland.Callback
124	async              bool
125	color              SurfaceColor
126	representation     surfaceRepresentation
127	kind               uint32
128	// fade is 1 - the wp_alpha_modifier_v1 multiplier (sticky).
129	fade float32
130	// barrier and wait are fifo requests; at the commit timestamp.
131	barrier, wait bool
132	at            time.Time
133	// damage is in surface (logical) pixels, bufDamage in buffer pixels.
134	damage, bufDamage []ports.Rect
135	// feedback are wp_presentation feedbacks for this commit.
136	feedback []*presentationtime.WpPresentationFeedback
137	// sync is the commit's explicit-sync points, with the wait for its
138	// acquire point.
139	sync *commitSync
140}
141
142// commitSync is a commit's explicit-sync points and acquire wait.
143type commitSync struct {
144	acquire, release syncPoint
145	wait             *syncWait
146	fence            *os.File // exported before application; owned until apply/drop
147}
148
149// damage is what one commit changed in a surface's buffer.
150type damage struct {
151	full  bool
152	rects []ports.Rect
153}
154
155// maxDamageRects bounds one commit's rects: past it the commit is full.
156const maxDamageRects = 32
157
158// subState is the subsurface tree: parent is set on subsurfaces, children
159// are ordered bottom to top, below marks children under their parent.
160type subState struct {
161	// role is the live wl_subsurface, nil when the surface has none.
162	role                  *wayland.Subsurface
163	parent                *surface
164	children              []*surface // structural children
165	pendingLayout, layout []childLayout
166	synced                bool // explicit synchronization mode
167}
168
169// root is the top of the surface's subsurface tree.
170func (s *surface) root() *surface {
171	for s.sub.parent != nil {
172		s = s.sub.parent
173	}
174	return s
175}
176
177// windowID is the mapped window or layer the surface draws, or 0.
178func (s *surface) windowID() ports.WindowID {
179	if s.xdg != nil && s.xdg.window != nil && s.xdg.window.mapped {
180		return s.xdg.window.id
181	}
182	if s.layer != nil && s.layer.mapped {
183		return s.layer.id
184	}
185	if s.lock != nil && s.lock.mapped {
186		return s.lock.id
187	}
188	return 0
189}
190
191// outputColor converts the committed protocol description without allocating.
192func (s *surface) outputColor() ports.SurfaceColor {
193	return ports.SurfaceColor{TF: uint8(s.color.TF), Primaries: uint8(s.color.Primaries), MaxCLL: uint16(s.color.MaxCLL), MaxFALL: uint16(s.color.MaxFALL), Coefficients: s.representation.coefficients, Range: s.representation.rangeValue, Chroma: s.representation.chroma}
194}
195
196func (s *surface) contentWithColor() ports.SurfaceContent {
197	c := s.content
198	c.Surface, c.Version = s.identity, s.version
199	c.Color = s.outputColor()
200	// A subsurface asks for tearing itself: the output may scan it out.
201	c.Async = s.async
202	return c
203}
204
205// tree is the root's content with its subsurfaces flattened.
206func (s *surface) tree(id ports.WindowID) ports.SurfaceContent {
207	c := s.contentWithColor()
208	c.ID = id
209	c.ContentType = ports.ContentType(s.contentKind)
210	if s.treeDirty || s.cachedTree == nil {
211		count := s.treeCount()
212		children := make([]ports.Subsurface, 0, count)
213		for _, item := range s.sub.layout {
214			item.child.appendTree(&children, item.x, item.y, item.below)
215		}
216		s.cachedTree, s.treeDirty = children, false
217	}
218	c.Children = s.cachedTree
219	if s.xdg != nil {
220		c.Geometry = s.xdg.geometry
221	}
222	return c
223}
224
225func (s *surface) treeCount() int {
226	n := 0
227	for _, item := range s.sub.layout {
228		if item.child.has {
229			n++
230		}
231		n += item.child.treeCount()
232	}
233	return n
234}
235
236// appendTree flattens a subsurface at (x, y) from the root: its children
237// below it, itself, then its children above.
238func (s *surface) appendTree(out *[]ports.Subsurface, x, y int, below bool) {
239	for _, item := range s.sub.layout {
240		if item.below {
241			item.child.appendTree(out, x+item.x, y+item.y, below)
242		}
243	}
244	if s.has {
245		*out = append(*out, ports.Subsurface{X: x, Y: y, Below: below, SurfaceContent: s.contentWithColor()})
246	}
247	for _, item := range s.sub.layout {
248		if !item.below {
249			item.child.appendTree(out, x+item.x, y+item.y, below)
250		}
251	}
252}
253
254// redraw sends the tree of a mapped root to the outputs. Only a commit
255// of the root's own buffer with no other change keeps partial damage.
256func (s *surface) redraw() {
257	r := s.root()
258	if id := r.windowID(); id != 0 && s.server.channels.Contents != nil {
259		d := damage{full: true}
260		if s == r && len(r.sub.children) == 0 {
261			d = r.committed
262		}
263		s.server.emitContent(r.tree(id), d)
264	}
265}
266
267// detach removes a subsurface from its parent and redraws the parent.
268func (s *surface) detach() {
269	p := s.sub.parent
270	if p == nil {
271		return
272	}
273	for i, ch := range p.sub.children {
274		if ch == s {
275			p.sub.children = append(p.sub.children[:i:i], p.sub.children[i+1:]...)
276			break
277		}
278	}
279	p.sub.pendingLayout = removeLayout(p.sub.pendingLayout, s)
280	p.sub.layout = removeLayout(p.sub.layout, s)
281	p.root().treeDirty = true
282	for _, u := range p.queue {
283		u.layout = removeLayout(u.layout, s)
284	}
285	s.sub.parent = nil
286	// Parent updates cannot make a detached child's bound commits visible.
287	s.dropQueue()
288	s.flushDesync()
289	p.redraw()
290	p.emitInput()
291	s.sendScale()
292}
293
294func (s *surface) Destroy(*wayland.Surface) {
295	s.destroyed = true
296	for _, fb := range s.next.feedback {
297		discard(fb)
298	}
299	s.next.feedback = nil
300	s.tearing = nil // the control becomes inert
301	s.colorControl = nil
302	s.representationControl = nil
303	s.dropQueue()
304	if s.server.cursorSurface == s {
305		// The pointer keeps no cursor until the client sets another.
306		s.server.cursorSurface = nil
307		s.server.setCursor(ports.CursorChange{Hidden: true})
308	}
309	s.detach()
310	for _, ch := range s.sub.children {
311		ch.sub.parent = nil
312		ch.dropQueue()
313		ch.flushDesync()
314	}
315	s.sub.children = nil
316	s.sub.layout, s.sub.pendingLayout = nil, nil
317	for _, cb := range s.next.callbacks {
318		cb.Destroy()
319	}
320	s.next.callbacks = nil
321	// The client may reuse the buffer on another surface.
322	s.dropSync(s.next.sync)
323	s.next.sync = nil
324	if s.current != nil {
325		s.server.releaseBuffer(s, s.current, s.hold)
326		s.hold = syncHold{}
327	}
328	s.current, s.next.buffer = nil, nil
329	if s.role != nil {
330		s.role(false)
331	}
332	if c := s.server.constraints[s]; c != nil {
333		s.server.dropConstraint(c)
334	}
335}
336func (s *surface) Attach(_ *wayland.Surface, b *wayland.Buffer, _, _ int32) {
337	s.next.buffer = b
338	s.next.attached = true
339}
340func (s *surface) Frame(r *wayland.Surface, id uint32) {
341	cb, err := wayland.NewCallback(r.Client(), 1, id, struct{}{})
342	if err == nil {
343		s.next.callbacks = append(s.next.callbacks, cb)
344	}
345}
346func (s *surface) Commit(*wayland.Surface) {
347	if s.next.attached && s.next.buffer != nil {
348		s.bufferCommitted = true
349	}
350	if s.lock != nil && !s.lock.checkCommit() {
351		return
352	}
353	if s.layer != nil && s.next.attached && s.next.buffer != nil && !s.layer.acked {
354		s.layer.resource.PostError(uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidSurfaceState), "buffer before configure ack")
355		return
356	}
357	if s.xdg != nil && s.next.attached && s.next.buffer != nil && !s.xdg.acked {
358		s.xdg.resource.PostError(uint32(xdgshell.SurfaceErrorUnconfiguredBuffer), "buffer before initial configure ack")
359		return
360	}
361	if !s.checkSyncCommit() || !s.checkRepresentationCommit() {
362		return
363	}
364	s.takeSyncPoints()
365	// Each request creates an ordered content update. A synchronized update
366	// can only be reached through a parent's committed dependency graph.
367	s.queueUpdate()
368	s.server.tickFifo(time.Now(), nil)
369}
370
371// applyCommit makes only the captured update current. Live pending requests
372// remain untouched, even when this update waited behind another commit.
373func (s *surface) applyCommit(u *update) {
374	oldW, oldH, oldSource, oldTransform := s.content.LogicalW, s.content.LogicalH, s.content.Source, s.content.Transform
375	oldColor, oldRepresentation := s.color, s.representation
376	fresh := u.attached && u.buffer != nil
377	s.commitFresh = fresh
378	if !s.commitSkipped {
379		s.queuedScale = u.scale
380		s.queuedBuffer = 0
381		if u.buffer != nil {
382			s.queuedBuffer = u.buffer.ID()
383		}
384	}
385	if u.barrier {
386		s.setBarrier(time.Now())
387	}
388	if s.contentKind != u.kind {
389		s.contentKind = u.kind
390		s.server.log.Info().Str("component", "wayland").Uint64("id", uint64(s.root().windowID())).Uint32("content_type", s.contentKind).Msg("content type")
391	}
392	if u.scale > 0 {
393		s.bufferScale = u.scale
394	}
395	s.transform = u.transform
396	oldOpaque := s.content.Opaque
397	if u.opaqueSet {
398		s.opaque = u.opaque
399	}
400	oldFade := s.content.Fade
401	hinted := s.async != u.async || s.color != u.color || s.representation != u.representation
402	if s.async != u.async {
403		// Contents carry the hint of every surface in the tree.
404		s.root().treeDirty = true
405	}
406	s.async = u.async
407	s.color = u.color
408	s.representation = u.representation
409	if s.lock != nil && s.commitSkipped {
410		// A discarded replacement cannot change retained lock geometry, even
411		// if its viewport was destroyed before capture or before application.
412	} else if u.vp != nil && (s.lock != nil || u.vp.resource != nil && u.vp.resource.Resource.Alive()) {
413		s.committedViewport.destW, s.committedViewport.destH, s.committedViewport.dest = u.vpW, u.vpH, u.vpSet
414		s.committedViewport.src, s.committedViewport.crop = u.vpSrc, u.vpCrop
415	} else {
416		s.committedViewport.dest, s.committedViewport.crop = false, false
417	}
418	cs := u.sync
419	if u.attached {
420		if s.current != nil && (u.buffer == nil || s.current.Resource != u.buffer.Resource) {
421			s.server.releaseBuffer(s, s.current, s.hold)
422		} else if cs != nil || u.buffer == nil {
423			// Same buffer again, or detached: the old points are done
424			// with once the new ones take over.
425			s.server.releaseBufferSync(s, s.hold)
426		}
427		s.hold = syncHold{}
428		s.current = u.buffer
429	}
430	s.applySync(cs)
431	if u.inputSet {
432		s.inputAll, s.inputRects = u.inputAll, u.inputRects
433	}
434	if len(u.callbacks) > 0 {
435		s.server.queueFrames(s, u.callbacks)
436	}
437	if s.lock != nil && !s.commitSkipped && !s.lock.checkCaptured(u) {
438		return
439	}
440	if s.role != nil {
441		if u.layer != nil && s.layer == u.layer {
442			u.layer.commitState(u.layerNext, s.current != nil)
443		} else {
444			s.role(s.current != nil)
445		}
446	}
447	if fresh {
448		if state, ok := s.server.buffers[s.current.Resource]; ok {
449			if c, ok := state.content(0); ok {
450				if !s.validateViewport(c.Width, c.Height) {
451					return
452				}
453				c.Transform = s.transform
454				c.Source, _ = s.source(c.Width, c.Height)
455				c.LogicalW, c.LogicalH = s.logicalSize(c.Width, c.Height)
456				s.formatOpaque = c.Opaque
457				c.Opaque = (c.Opaque || s.opaqueCovers(c.LogicalW, c.LogicalH)) && u.fade == 0
458				c.Fade = u.fade
459				resized := !s.has || c.Width != s.content.Width || c.Height != s.content.Height || c.LogicalW != s.content.LogicalW || c.LogicalH != s.content.LogicalH
460				s.commitDamage(u, true, resized, c.Width, c.Height)
461				if s.sub.parent == nil && (c.Width != s.lastW || c.Height != s.lastH) {
462					s.lastW, s.lastH = c.Width, c.Height
463					s.server.log.Info().Uint64("id", uint64(s.windowID())).Int("w", c.Width).Int("h", c.Height).Msg("buffer size")
464				}
465				c.Acquire = s.hold.acquire
466				s.content, s.has = c, true
467			} else if shm, ok := state.(*buffer); ok {
468				shm.pool.shm.PostError(uint32(wayland.ShmErrorInvalidFd), "SHM backing file truncated")
469			}
470			// Buffers are read in place: released when the next one
471			// replaces them.
472		}
473	}
474	damaged := !fresh && s.current != nil && (len(u.damage) > 0 || len(u.bufDamage) > 0)
475	if !fresh {
476		s.commitDamage(u, false, false, 0, 0)
477		// A NULL attach is exempt from out_of_buffer: its content is
478		// cleared below, so the retained buffer is not validated.
479		if s.has && s.current != nil {
480			if !s.validateViewport(s.content.Width, s.content.Height) {
481				return
482			}
483			s.content.Transform = s.transform
484			s.content.Source, _ = s.source(s.content.Width, s.content.Height)
485			s.content.LogicalW, s.content.LogicalH = s.logicalSize(s.content.Width, s.content.Height)
486			s.content.Opaque = (s.formatOpaque || s.opaqueCovers(s.content.LogicalW, s.content.LogicalH)) && u.fade == 0
487			s.content.Fade = u.fade
488		}
489	}
490	if s.current == nil {
491		s.content, s.has = ports.SurfaceContent{}, false
492	}
493	if fresh || damaged || oldW != s.content.LogicalW || oldH != s.content.LogicalH || oldSource != s.content.Source || oldColor != s.color || oldRepresentation != s.representation || oldTransform != s.content.Transform || oldOpaque != s.content.Opaque || oldFade != s.content.Fade {
494		s.version++
495		s.root().treeDirty = true
496	}
497	// The layout belongs to this update, not to the latest requests.
498	moved := !sameLayout(s.sub.layout, u.layout)
499	if moved {
500		s.root().treeDirty = true
501	}
502	// Queued updates can still reference the previous layout. Never mutate it.
503	if moved {
504		s.sub.layout = u.layout
505	}
506	geometry := false
507	if s.xdg != nil && s.xdg == u.xdg && u.geometry != s.xdg.geometry {
508		s.xdg.geometry, geometry = u.geometry, true
509	}
510	s.commitConstraint(u.cons, u.region, geometry)
511	s.emitInput()
512	if s.xdg != nil && s.xdg.window != nil {
513		s.xdg.window.afterCommit()
514	}
515	if s.lock != nil {
516		// Role callbacks precede content construction; publish lock placement
517		// only once validated current dimensions are available.
518		s.server.lockSurfaceChanged()
519	}
520	reshaped := s.has && (s.content.LogicalW != oldW || s.content.LogicalH != oldH || s.content.Source != oldSource || s.content.Transform != oldTransform)
521	// Opacity changes how every pixel blends, so it repaints the whole surface.
522	opacity := s.has && (s.content.Opaque != oldOpaque || s.content.Fade != oldFade)
523	drawn := fresh || damaged || moved || geometry || hinted || reshaped || opacity || s.sub.parent != nil
524	if drawn {
525		if moved || geometry || reshaped || opacity {
526			s.committed = damage{full: true}
527		}
528		if s.server.applyingGraph {
529			s.server.graphDrawn = true
530		} else {
531			s.redraw()
532		}
533	}
534	fb := u.feedback
535	if s.server.applyingGraph {
536		s.server.graphFeedback = append(s.server.graphFeedback, graphFeedback{s, fb, fresh})
537	} else {
538		s.commitFeedback(fb, fresh)
539	}
540}
541func (s *surface) Damage(_ *wayland.Surface, x, y, w, h int32) {
542	if w > 0 && h > 0 && len(s.next.damage) <= maxDamageRects {
543		s.next.damage = append(s.next.damage, ports.Rect{X: int(x), Y: int(y), W: int(w), H: int(h)})
544	}
545}
546func (s *surface) DamageBuffer(_ *wayland.Surface, x, y, w, h int32) {
547	if w > 0 && h > 0 && len(s.next.bufDamage) <= maxDamageRects {
548		s.next.bufDamage = append(s.next.bufDamage, ports.Rect{X: int(x), Y: int(y), W: int(w), H: int(h)})
549	}
550}
551
552// commitDamage turns the requested damage into buffer pixels of a bw×bh
553// buffer. Surface damage scales by the buffer/logical ratio, rounded out.
554// A new size, too many rects or none at all with a new buffer (clients
555// that do not report damage) is a full change.
556func (s *surface) commitDamage(u *update, fresh, resized bool, bw, bh int) {
557	surf, buf := u.damage, u.bufDamage
558	if !fresh {
559		s.committed = damage{}
560		return
561	}
562	if resized || len(surf)+len(buf) == 0 || len(surf)+len(buf) > maxDamageRects {
563		s.committed = damage{full: true}
564		return
565	}
566	lw, lh := s.logicalSize(bw, bh)
567	d := damage{rects: make([]ports.Rect, 0, len(surf)+len(buf))}
568	bounds := image.Rect(0, 0, bw, bh)
569	add := func(r image.Rectangle) {
570		if r = r.Intersect(bounds); !r.Empty() {
571			d.rects = append(d.rects, ports.Rect{X: r.Min.X, Y: r.Min.Y, W: r.Dx(), H: r.Dy()})
572		}
573	}
574	for _, r := range buf {
575		add(image.Rect(r.X, r.Y, r.X+r.W, r.Y+r.H))
576	}
577	// Surface damage maps through the crop, in the transformed buffer's
578	// axes, then back through the buffer transform.
579	tw, th := s.transformedSize(bw, bh)
580	sx, sy, sw, sh := 0.0, 0.0, float64(tw), float64(th)
581	if s.committedViewport.crop {
582		v := s.committedViewport.src
583		scale := float64(max(s.bufferScale, 1)) / 256
584		sx, sy, sw, sh = float64(v[0])*scale, float64(v[1])*scale, float64(v[2])*scale, float64(v[3])*scale
585	}
586	for _, r := range surf {
587		if lw <= 0 || lh <= 0 {
588			s.committed = damage{full: true}
589			return
590		}
591		// Clients may damage INT32_MAX-sized rects: clamp before scaling.
592		x0, y0 := max(r.X, 0), max(r.Y, 0)
593		x1, y1 := min(r.X+min(r.W, lw), lw), min(r.Y+min(r.H, lh), lh)
594		ax, ay := s.transform.ToBuffer(sx+float64(x0)*sw/float64(lw), sy+float64(y0)*sh/float64(lh), float64(tw), float64(th))
595		bx, by := s.transform.ToBuffer(sx+float64(x1)*sw/float64(lw), sy+float64(y1)*sh/float64(lh), float64(tw), float64(th))
596		add(image.Rect(int(math.Floor(min(ax, bx))), int(math.Floor(min(ay, by))), int(math.Ceil(max(ax, bx))), int(math.Ceil(max(ay, by)))))
597	}
598	s.committed = d
599}
600
601// SetOpaqueRegion copies the region: a nil region means nothing is opaque.
602func (s *surface) SetOpaqueRegion(_ *wayland.Surface, reg *wayland.Region) {
603	s.next.opaqueSet = true
604	s.next.opaque = nil
605	if reg != nil && reg.Resource != nil {
606		if g := s.server.regions[reg.Resource]; g != nil {
607			s.next.opaque = slices.Clone(g.rects)
608		}
609	}
610}
611
612func (s *surface) SetBufferTransform(r *wayland.Surface, v int32) {
613	if v < 0 || v > 7 {
614		r.PostError(uint32(wayland.SurfaceErrorInvalidTransform), "invalid buffer transform")
615		return
616	}
617	s.next.transform = ports.BufferTransform(v)
618}
619
620// opaqueCovers reports whether the committed opaque region covers the whole
621// w×h surface. A partial region is ignored: it is only an optimization hint.
622func (s *surface) opaqueCovers(w, h int) bool {
623	return w > 0 && h > 0 && covers(s.opaque, ports.Rect{W: w, H: h})
624}
625
626// maxCoverRects bounds the region covers checks: beyond it, not covered.
627const maxCoverRects = 16
628
629// covers reports whether the union of rects contains all of r. It tests
630// one point per cell of the grid the rect edges draw.
631func covers(rects []ports.Rect, r ports.Rect) bool {
632	if len(rects) == 0 || len(rects) > maxCoverRects {
633		return false
634	}
635	var xsBuf, ysBuf [2*maxCoverRects + 2]int
636	xs, ys := append(xsBuf[:0], r.X, r.X+r.W), append(ysBuf[:0], r.Y, r.Y+r.H)
637	for _, c := range rects {
638		if c.X > r.X && c.X < r.X+r.W {
639			xs = append(xs, c.X)
640		}
641		if e := c.X + c.W; e > r.X && e < r.X+r.W {
642			xs = append(xs, e)
643		}
644		if c.Y > r.Y && c.Y < r.Y+r.H {
645			ys = append(ys, c.Y)
646		}
647		if e := c.Y + c.H; e > r.Y && e < r.Y+r.H {
648			ys = append(ys, e)
649		}
650	}
651	slices.Sort(xs)
652	slices.Sort(ys)
653	for i := 1; i < len(xs); i++ {
654		for j := 1; j < len(ys); j++ {
655			if xs[i] == xs[i-1] || ys[j] == ys[j-1] {
656				continue
657			}
658			x, y := xs[i-1], ys[j-1]
659			if !slices.ContainsFunc(rects, func(c ports.Rect) bool {
660				return x >= c.X && x < c.X+c.W && y >= c.Y && y < c.Y+c.H
661			}) {
662				return false
663			}
664		}
665	}
666	return true
667}
668func (s *surface) SetBufferScale(r *wayland.Surface, v int32) {
669	if v < 1 {
670		r.PostError(uint32(wayland.SurfaceErrorInvalidScale), "buffer scale must be positive")
671		return
672	}
673	s.next.scale = int(v)
674}
675func (*surface) Offset(*wayland.Surface, int32, int32) {}
676func (*surface) GetRelease(*wayland.Surface, uint32)   {}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 23

Tags 10

↑↓ moveenter openesc close