v0.3.0 cf370a0

extforeigntoplevel.go RawHistoryFolder

1package wayland
2
3import (
4	"slices"
5	"strconv"
6
7	ext "github.com/bnema/go-wayland-bindings/server/extforeigntoplevellist"
8	"github.com/bnema/neferwl/internal/ports"
9	"github.com/bnema/purego-libwayland/server"
10)
11
12// ext-foreign-toplevel-list lists mapped toplevels, read-only: title, app_id
13// and a stable identifier. xdg-desktop-portal-wlr lists the windows it can
14// share with it, and ext_foreign_toplevel_image_capture_source_manager_v1
15// takes its handles (window capture, imagecopy.go). It follows the same
16// window state and session-protection rules as wlr-foreign-toplevel.
17
18type extToplevelList struct {
19	s       *Server
20	res     *ext.ExtForeignToplevelListV1
21	stopped bool
22	handles map[ports.WindowID]*extToplevelHandle
23}
24
25// extToplevelHandle is one window seen by one list, with what it was last
26// sent so that only changes go out.
27type extToplevelHandle struct {
28	res          *ext.ExtForeignToplevelHandleV1
29	window       ports.WindowID
30	gen          uint32 // the window's mapping it was made for
31	closed       bool
32	title, appID string
33}
34
35func (extToplevelHandle) Destroy(*ext.ExtForeignToplevelHandleV1) {}
36
37// toplevelIdentifier is the ext-foreign-toplevel identifier of a window's
38// mapping: window IDs are never reused and a remap counts a new mapping, so
39// neither is the identifier.
40func toplevelIdentifier(id ports.WindowID, gen uint32) string {
41	return "neferwl-" + strconv.FormatUint(uint64(id), 10) + "-" + strconv.FormatUint(uint64(gen), 10)
42}
43
44func registerExtForeignToplevel(d *server.Display, s *Server) error {
45	return ext.NewExtForeignToplevelListV1Global(d, 1, func(c server.Client, v, id uint32) {
46		l := &extToplevelList{s: s, handles: map[ports.WindowID]*extToplevelHandle{}}
47		r, err := ext.NewExtForeignToplevelListV1(c, int32(v), id, l)
48		if err != nil {
49			return
50		}
51		l.res = r
52		s.extToplevelLists = append(s.extToplevelLists, l)
53		r.OnDestroy = func() { s.extToplevelLists = removeItem(s.extToplevelLists, l) }
54		for _, id := range s.mappedToplevels() {
55			l.refresh(s.windows[id])
56		}
57	})
58}
59
60// Stop ends the list: finished, and no more toplevel events.
61func (l *extToplevelList) Stop(*ext.ExtForeignToplevelListV1) {
62	if l.stopped {
63		return
64	}
65	l.stopped = true
66	l.res.SendFinished()
67}
68
69func (*extToplevelList) Destroy(*ext.ExtForeignToplevelListV1) {}
70
71// mappedToplevels lists the mapped toplevels, by ID.
72func (s *Server) mappedToplevels() []ports.WindowID {
73	ids := make([]ports.WindowID, 0, len(s.windows))
74	for id, w := range s.windows {
75		if w.toplevel != nil && w.mapped {
76			ids = append(ids, id)
77		}
78	}
79	slices.Sort(ids)
80	return ids
81}
82
83// refresh announces w or sends what changed since the last done. After
84// stop no new toplevel is announced; the handles already sent stay live.
85func (l *extToplevelList) refresh(w *window) {
86	if l.s.protected() || !l.res.Alive() {
87		return
88	}
89	h := l.handles[w.id]
90	if h != nil && (h.closed || h.gen != w.gen) {
91		l.closed(w.id)
92		h = nil
93	}
94	fresh := h == nil
95	if fresh && l.stopped {
96		return
97	}
98	if fresh {
99		h = &extToplevelHandle{window: w.id, gen: w.gen}
100		r, err := ext.NewExtForeignToplevelHandleV1(l.res.Client(), l.res.Version(), 0, h)
101		if err != nil {
102			return
103		}
104		h.res = r
105		l.handles[w.id] = h
106		l.res.SendToplevel(r)
107		r.SendIdentifier(toplevelIdentifier(w.id, w.gen))
108	}
109	if !h.res.Alive() {
110		return
111	}
112	changed := fresh
113	if fresh || h.title != w.title {
114		h.title = w.title
115		h.res.SendTitle(w.title)
116		changed = true
117	}
118	if fresh || h.appID != w.appID {
119		h.appID = w.appID
120		h.res.SendAppId(w.appID)
121		changed = true
122	}
123	if changed {
124		h.res.SendDone()
125	}
126}
127
128// closed sends closed for a window; like wlr-foreign-toplevel, a close
129// during session protection is sent at the unlock.
130func (l *extToplevelList) closed(id ports.WindowID) {
131	h := l.handles[id]
132	if h == nil {
133		return
134	}
135	h.closed = true
136	if l.s.protected() {
137		return
138	}
139	delete(l.handles, id)
140	if h.res.Alive() {
141		h.res.SendClosed()
142	}
143}
144
145// extHandleWindow is the window mapping behind an
146// ext_foreign_toplevel_handle_v1 of any list, while it is open.
147func (s *Server) extHandleWindow(res *server.Resource) (ports.WindowID, uint32, bool) {
148	for _, l := range s.extToplevelLists {
149		for id, h := range l.handles {
150			if h.res.Resource == res && !h.closed {
151				return id, h.gen, true
152			}
153		}
154	}
155	return 0, 0, false
156}

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