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}