foreign.go RawHistoryFolder

1package wayland
2
3import (
4	"crypto/rand"
5	"slices"
6
7	"github.com/bnema/go-wayland-bindings/server/wayland"
8	"github.com/bnema/go-wayland-bindings/server/xdgforeign"
9	"github.com/bnema/purego-libwayland/server"
10)
11
12// xdg-foreign-unstable-v2 lets a client make its toplevel the child of
13// another client's toplevel. The desktop portal uses it: the application
14// exports its window and passes the handle over D-Bus, the portal backend
15// imports it and parents its dialog to it, so the dialog floats over the
16// application that asked (window.parent) instead of opening as a new tile.
17
18// foreignExport is one zxdg_exported_v2: the handle of a toplevel.
19type foreignExport struct {
20	handle  string
21	window  *window
22	imports []*foreignImport
23}
24
25// foreignImport is one zxdg_imported_v2 and the toplevels it parented.
26type foreignImport struct {
27	res      *xdgforeign.ZxdgImportedV2
28	export   *foreignExport // nil once invalid
29	children []*window
30}
31
32func registerForeign(d *server.Display, s *Server) error {
33	if err := xdgforeign.NewZxdgExporterV2Global(d, 1, func(c server.Client, v, id uint32) {
34		_, _ = xdgforeign.NewZxdgExporterV2(c, int32(v), id, foreignExporter{s})
35	}); err != nil {
36		return err
37	}
38	return xdgforeign.NewZxdgImporterV2Global(d, 1, func(c server.Client, v, id uint32) {
39		_, _ = xdgforeign.NewZxdgImporterV2(c, int32(v), id, foreignImporter{s})
40	})
41}
42
43// toplevelOf is the xdg_toplevel window of a wl_surface, or nil.
44func (s *Server) toplevelOf(surf *wayland.Surface) *window {
45	st := s.surfaceOf(surf)
46	if st == nil || st.xdg == nil || st.xdg.window == nil || st.xdg.window.toplevel == nil {
47		return nil
48	}
49	return st.xdg.window
50}
51
52type foreignExporter struct{ s *Server }
53
54func (foreignExporter) Destroy(*xdgforeign.ZxdgExporterV2) {}
55
56func (e foreignExporter) ExportToplevel(r *xdgforeign.ZxdgExporterV2, id uint32, surf *wayland.Surface) {
57	w := e.s.toplevelOf(surf)
58	if w == nil {
59		r.PostError(uint32(xdgforeign.ZxdgExporterV2ErrorInvalidSurface), "surface is not an xdg_toplevel")
60		return
61	}
62	ex := &foreignExport{handle: rand.Text(), window: w}
63	res, err := xdgforeign.NewZxdgExportedV2(r.Client(), r.Version(), id, exportedHandler{})
64	if err != nil {
65		return
66	}
67	e.s.foreignExports[ex.handle] = ex
68	res.OnDestroy = func() { e.s.revokeExport(ex) }
69	res.SendHandle(ex.handle)
70}
71
72type exportedHandler struct{}
73
74func (exportedHandler) Destroy(*xdgforeign.ZxdgExportedV2) {}
75
76type foreignImporter struct{ s *Server }
77
78func (foreignImporter) Destroy(*xdgforeign.ZxdgImporterV2) {}
79
80// ImportToplevel imports a handle; an unknown one is destroyed at once, as
81// the protocol asks.
82func (i foreignImporter) ImportToplevel(r *xdgforeign.ZxdgImporterV2, id uint32, handle string) {
83	imp := &foreignImport{}
84	res, err := xdgforeign.NewZxdgImportedV2(r.Client(), r.Version(), id, importedHandler{s: i.s, imp: imp})
85	if err != nil {
86		return
87	}
88	imp.res = res
89	res.OnDestroy = func() { imp.invalidate(false) }
90	ex := i.s.foreignExports[handle]
91	if ex == nil {
92		res.SendDestroyed()
93		return
94	}
95	imp.export = ex
96	ex.imports = append(ex.imports, imp)
97}
98
99type importedHandler struct {
100	s   *Server
101	imp *foreignImport
102}
103
104func (importedHandler) Destroy(*xdgforeign.ZxdgImportedV2) {}
105
106// SetParentOf makes the imported toplevel the parent of the client's own
107// toplevel, as xdg_toplevel.set_parent.
108func (h importedHandler) SetParentOf(r *xdgforeign.ZxdgImportedV2, surf *wayland.Surface) {
109	child := h.s.toplevelOf(surf)
110	if child == nil || child.toplevel.Client() != r.Client() {
111		r.PostError(uint32(xdgforeign.ZxdgImportedV2ErrorInvalidSurface), "surface is not an xdg_toplevel of this client")
112		return
113	}
114	ex := h.imp.export
115	if ex == nil || ex.window == child {
116		return
117	}
118	child.setParent(ex.window)
119	if !slices.Contains(h.imp.children, child) {
120		h.imp.children = append(h.imp.children, child)
121	}
122}
123
124// invalidate ends the relationships an import set up. send tells the client
125// (destroyed) when the export, not the import, went away.
126func (imp *foreignImport) invalidate(send bool) {
127	for _, c := range imp.children {
128		if imp.export != nil && c.parent == imp.export.window {
129			c.setParent(nil)
130		}
131	}
132	imp.children = nil
133	if ex := imp.export; ex != nil {
134		ex.imports = removeItem(ex.imports, imp)
135		imp.export = nil
136	}
137	if send && imp.res.Alive() {
138		imp.res.SendDestroyed()
139	}
140}
141
142// revokeExport drops a handle and every relationship made with it.
143func (s *Server) revokeExport(ex *foreignExport) {
144	if s.foreignExports[ex.handle] == ex {
145		delete(s.foreignExports, ex.handle)
146	}
147	for len(ex.imports) > 0 {
148		ex.imports[0].invalidate(true)
149	}
150}
151
152// foreignToplevelGone revokes the handles of a destroyed toplevel.
153func (s *Server) foreignToplevelGone(w *window) {
154	for _, ex := range s.foreignExports {
155		if ex.window == w {
156			s.revokeExport(ex)
157		}
158	}
159}

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