feat/data-dnd e05cb15

security.go RawHistoryFolder

1package drm
2
3import (
4	"context"
5	"errors"
6	"time"
7
8	"github.com/bnema/neferwl/internal/ports"
9)
10
11var errSecurityScene = errors.New("scene security epoch changed or output unprepared")
12
13// observeSecurity is output-owner only. The channel wakes; the coherent gate
14// decides admission, including shutdown paths that never received the wake.
15func (o *Output) observeSecurity() bool {
16	if o.Security == nil {
17		return false
18	}
19	state := o.Security.Snapshot()
20	if state == o.securityState {
21		return false
22	}
23	o.securityState = state
24	o.securityPrepared = false
25	o.protected = state.Protected
26	o.protectSucceeded() // a new epoch gets an immediate first attempt
27	if o.cursor != nil {
28		o.cursor.image, o.cursor.later = false, nil
29	}
30	return true
31}
32
33func (o *Output) sceneCurrent(scene ports.Scene) bool {
34	o.observeSecurity()
35	return o.Security == nil || scene.Security == o.securityState
36}
37
38func (o *Output) invalidateSecurity() {
39	o.securityPrepared = false
40	if o.protected {
41		o.securityInvalid = true
42	}
43}
44
45func (o *Output) securityEvent(ctx context.Context, event ports.SecurityBackendEvent) error {
46	if o.SecurityEvents == nil || o.Instance == 0 {
47		return nil
48	}
49	select {
50	case o.SecurityEvents <- event:
51		return nil
52	case <-ctx.Done():
53		return ctx.Err()
54	}
55}
56
57func (o *Output) sendSecurityInvalid(ctx context.Context) error {
58	if !o.securityInvalid {
59		return nil
60	}
61	state := o.securityState
62	if err := o.securityEvent(ctx, ports.SecurityOutputInvalidated{Generation: state.Generation, Instance: o.Instance}); err != nil {
63		return err
64	}
65	o.securityInvalid = false
66	return nil
67}
68
69func (o *Output) prepareSecurity(ctx context.Context, r ports.Renderer) error {
70	o.observeSecurity()
71	if !o.protected {
72		return nil
73	}
74	if err := o.sendSecurityInvalid(ctx); err != nil {
75		return err
76	}
77	state := o.securityState
78	if err := o.protectedModeset(ctx, r); err != nil {
79		return err
80	}
81	// A gate transition during the blocking clear cannot certify a new epoch.
82	if o.observeSecurity() || o.securityState != state {
83		return nil
84	}
85	o.securityPrepared = true
86	kind := ports.ProtectionProtectedFrame
87	if o.off {
88		kind = ports.ProtectionInactiveOutput
89	}
90	// Separate non-merging channel; presentation feedback is not evidence.
91	// Backpressure may outlive an epoch. Revalidate on each bounded send
92	// attempt; the ledger also rejects a transition racing the final send.
93	if o.SecurityEvents == nil || o.Instance == 0 {
94		return nil
95	}
96	timer := time.NewTimer(time.Millisecond)
97	defer timer.Stop()
98	proof := ports.SecurityOutputProof{Proof: ports.OutputProtection{Generation: state.Generation, Instance: o.Instance, Kind: kind}}
99	for {
100		if o.observeSecurity() || o.securityState != state {
101			return nil
102		}
103		select {
104		case o.SecurityEvents <- proof:
105			return nil
106		case <-ctx.Done():
107			return ctx.Err()
108		case <-timer.C:
109			timer.Reset(time.Millisecond)
110		}
111	}
112}
113
114// Protected frames never enter client-fence KMS paths or capture pipelines.
115// Locker content is composed only after black preparation for this epoch.
116// They never wake core: the pendingFrame has no animating flag, so core falls
117// back to its frame timer while a lock is up. This is intentional.
118func (o *Output) submitProtectedFrame(ctx context.Context, r ports.Renderer, scene ports.Scene, surfaces map[ports.WindowID]ports.SurfaceContent, seen map[ports.WindowID]uint64) error {
119	if !o.securityPrepared || !o.sceneCurrent(scene) {
120		return errSecurityScene
121	}
122	scene.CaptureScene, scene.Capture = nil, nil
123	r.UseTarget(o.back)
124	done, err := r.Render(scene, surfaces)
125	if err != nil {
126		if done != nil {
127			done.Close()
128		}
129		return renderError{err}
130	}
131	// A transition while rendering must not put the obsolete scene on KMS.
132	if !o.sceneCurrent(scene) || !o.protected || !o.securityPrepared {
133		o.holdRead(done)
134		if done != nil {
135			done.Close()
136		}
137		return errSecurityScene
138	}
139	f := pendingFrame{security: scene.Security, frame: true, composed: true, shows: o.shownBy(scene, seen)}
140	err = o.commitWith(o.fbs[o.back], done, false, false, f, overlayWin{})
141	if err != nil {
142		o.holdRead(done)
143	}
144	if done != nil {
145		done.Close()
146	}
147	if err == nil {
148		o.front.forget()
149		o.back = 1 - o.back
150	}
151	return err
152}

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 25

Tags 10

↑↓ moveenter openesc close