v0.4.0 5f1ad00

lock.go RawHistoryFolder

1package neferclient
2
3import (
4	"errors"
5	"fmt"
6
7	"github.com/bnema/go-wayland-bindings/client/extsessionlock"
8	"github.com/bnema/wlturbo/wl"
9)
10
11// Lock is an ext-session-lock-v1 session lock. It reports [Handler.Locked]
12// once every output is protected and [Handler.LockFinished] if the compositor
13// refuses or ends it. Closing the connection or destroying surfaces never
14// unlocks; only [Lock.Unlock] after Locked does. Owner goroutine only.
15type Lock struct {
16	c        *Conn
17	gen      uint32
18	obj      *extsessionlock.ExtSessionLock
19	locked   bool
20	finished bool
21	done     bool
22}
23
24// Lock requests a session lock. At most one lock is live per connection.
25// The compositor answers through Handler.Locked or Handler.LockFinished.
26func (c *Conn) Lock() (*Lock, error) {
27	if c.closed {
28		return nil, ErrClosed
29	}
30	if c.curLock != nil {
31		return nil, errors.New("neferclient: session lock already acquired")
32	}
33	if err := c.ensureCore(); err != nil {
34		return nil, err
35	}
36	if err := c.ensureLockManager(); err != nil {
37		return nil, err
38	}
39	c.pause() // locked/finished may follow the request immediately
40	defer c.resume()
41	c.nextLock++
42	l := &Lock{c: c, gen: c.nextLock}
43	q, gen := c.q, l.gen
44	obj, err := c.g.lockMgr.Lock()
45	if err != nil {
46		return nil, err
47	}
48	obj.OnLocked(func() {
49		e := event{kind: evLocked, id: gen}
50		q.post(&e)
51	})
52	obj.OnFinished(func() {
53		e := event{kind: evLockFinished, id: gen}
54		q.post(&e)
55	})
56	l.obj = obj
57	c.curLock = l
58	return l, nil
59}
60
61// Locked reports whether the compositor confirmed the lock (applied by
62// Dispatch) and it has not finished.
63func (l *Lock) Locked() bool { return l.locked && !l.finished && !l.done }
64
65// NewSurface creates the lock surface covering the output with the given
66// registry name (see [Output.Global]). It becomes presentable after the first
67// Handler.Configure; a lock surface is always opaque and takes all input.
68func (l *Lock) NewSurface(output uint32) (*Surface, error) {
69	c := l.c
70	if c.closed {
71		return nil, ErrClosed
72	}
73	if l.done || l.finished {
74		return nil, errors.New("neferclient: lock ended")
75	}
76	e := c.entry(output)
77	if e == nil || !e.done {
78		return nil, fmt.Errorf("neferclient: lock output %d unavailable", output)
79	}
80	for _, other := range c.surfaces {
81		if other.lock == l && other.output == output {
82			return nil, fmt.Errorf("neferclient: output %d already has a lock surface", output)
83		}
84	}
85	c.pause()
86	defer c.resume()
87	s, err := c.newSurface(roleLock, 0, 0)
88	if err != nil {
89		return nil, err
90	}
91	ok := false
92	defer func() {
93		if !ok {
94			s.destroy()
95		}
96	}()
97	if s.lockSurf, err = l.obj.GetLockSurface(s.surf, e.proxy); err != nil {
98		return nil, err
99	}
100	s.lock, s.output = l, output
101	q, sid := c.q, uint32(s.id)
102	s.lockSurf.OnConfigure(func(serial, w, h uint32) {
103		ev := configureEvent(evLockConfigure, sid, serial, w, h)
104		q.post(&ev)
105	})
106	// ext-session-lock forbids the initial empty commit: the configure comes
107	// with get_lock_surface and the first commit carries a buffer.
108	if err = s.finishSurface(); err != nil {
109		return nil, err
110	}
111	ok = true
112	return s, nil
113}
114
115// Unlock sends unlock_and_destroy and roundtrips so the compositor processed
116// it before the caller closes anything. It is only valid after Handler.Locked.
117// The lock surfaces must be closed afterwards (or before: both are legal).
118func (l *Lock) Unlock() error {
119	if l.done {
120		return errors.New("neferclient: lock already destroyed")
121	}
122	if !l.Locked() {
123		return errors.New("neferclient: unlock before the locked event")
124	}
125	if err := l.obj.UnlockAndDestroy(); err != nil {
126		return err
127	}
128	l.done = true
129	l.c.curLock = nil
130	return l.c.Roundtrip()
131}
132
133// Close destroys a lock that never reached Locked (refused or abandoned). A
134// locked session is only ended by Unlock, so Close fails once Locked was
135// applied. Because the compositor may have sent locked before it processes the
136// destroy request (which would then be a protocol error), Close first
137// roundtrips and looks through the queued events: if a locked event for this
138// lock is already queued it is treated as applied (Handler.Locked is still
139// delivered by the next Dispatch) and Close fails with the same error.
140func (l *Lock) Close() error {
141	if l.done {
142		return nil
143	}
144	if !l.locked && !l.finished {
145		if err := l.c.Roundtrip(); err != nil {
146			return err
147		}
148		l.locked = l.locked || l.c.q.hasLocked(l.gen)
149	}
150	if l.locked && !l.finished {
151		return errors.New("neferclient: lock is locked: use Unlock")
152	}
153	l.done = true
154	l.c.curLock = nil
155	var err error
156	if l.obj != nil {
157		err = l.obj.Destroy()
158	}
159	return err
160}
161
162var _ wl.Proxy = (*frameCallback)(nil)

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 1

Tags 4

↑↓ moveenter openesc close