feat/freeze-key 753c87f

freeze.go RawHistoryFolder

1package wayland
2
3import (
4	"context"
5	"errors"
6	"fmt"
7	"slices"
8
9	extworkspace "github.com/bnema/go-wayland-bindings/client/extworkspace"
10
11	"github.com/bnema/nefercap/internal/ports"
12)
13
14// Freeze captures each output in ids once and keeps a private copy. Until
15// Thaw or Close, Capture serves those copies instead of the live screen:
16// whole outputs, regions and workspaces are cut from what the screen showed at
17// Freeze. An output that cannot be captured as is (a rotated one) is left out
18// and stays live. The returned frames, by output ID, own their storage and
19// stay valid until Thaw or Close; the caller must not modify them.
20func (s *Source) Freeze(ctx context.Context, ids []uint32) (map[uint32]ports.Frame, error) {
21	frozen := make(map[uint32]ports.Frame, len(ids))
22	for _, id := range ids {
23		f, err := s.Capture(ctx, ports.Target{OutputID: id})
24		if errors.Is(err, ports.ErrUnsupportedTransform) {
25			s.log.Info().Uint32("output", id).Msg("rotated output stays live")
26			continue
27		}
28		if err != nil {
29			return nil, fmt.Errorf("freeze output %d: %w", id, err)
30		}
31		frozen[id] = ownedCopy(f)
32	}
33	s.opMu.Lock()
34	defer s.opMu.Unlock()
35	if s.dead.Load() {
36		return nil, ports.ErrClosed
37	}
38	// Nothing is captured live until Thaw, unless an output stayed live: let
39	// the compositor drop the session and free the shared buffers, which a
40	// later live capture creates again.
41	s.dropSession()
42	if err := errors.Join(s.releaseBuffer(0, true), s.releaseBuffer(1, true)); err != nil {
43		s.terminate()
44		return nil, err
45	}
46	s.frozen = frozen
47	return frozen, nil
48}
49
50// Thaw makes Capture read the live screen again and drops the frozen copies,
51// except the one of each output in keep.
52func (s *Source) Thaw(keep ...uint32) {
53	s.opMu.Lock()
54	defer s.opMu.Unlock()
55	for id := range s.frozen {
56		if !slices.Contains(keep, id) {
57			delete(s.frozen, id)
58		}
59	}
60	if len(s.frozen) == 0 {
61		s.frozen = nil
62	}
63}
64
65// ownedCopy packs f into new storage, without stride padding.
66func ownedCopy(f ports.Frame) ports.Frame {
67	row := f.Width * ports.BytesPerPixel
68	pixels := make([]byte, row*f.Height)
69	for y := range f.Height {
70		copy(pixels[y*row:], f.Row(y))
71	}
72	return ports.Frame{Pixels: pixels, Width: f.Width, Height: f.Height, Stride: row, Format: f.Format}
73}
74
75// frozenTarget cuts t from the frozen frame of its output; ok is false when
76// that output is not frozen and is captured live. The caller holds opMu.
77func (s *Source) frozenTarget(t ports.Target) (_ ports.Frame, ok bool, _ error) {
78	f, ok := s.frozen[t.OutputID]
79	out := s.outputs[t.OutputID]
80	if !ok || out == nil {
81		return ports.Frame{}, false, nil
82	}
83	region := t.Region
84	if t.WorkspaceID != 0 {
85		if t.Region != (ports.Region{}) {
86			return ports.Frame{}, true, ports.ErrInvalidRegion
87		}
88		// As live without NeferWL's extension: only the workspace shown on
89		// this output at Freeze is what the copy shows.
90		w := s.ws.byID[t.WorkspaceID]
91		if w == nil || w.removed || w.state&extworkspace.STATE_ACTIVE == 0 || w.group == nil || !w.group.outputs[out.proxy.ID()] {
92			return ports.Frame{}, true, ports.ErrWorkspaceUnavailable
93		}
94		region = workspaceRegion(w, out)
95		if lw, lh := out.logicalSize(); region == (ports.Region{Width: lw, Height: lh}) {
96			region = ports.Region{}
97		}
98	}
99	if region == (ports.Region{}) {
100		return f, true, nil
101	}
102	f, err := cropFrame(f, region, out)
103	return f, true, err
104}

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 2

Tags 9

↑↓ moveenter openesc close