1package wayland
2
3import (
4 "github.com/bnema/go-wayland-bindings/server/extdatacontrol"
5 "github.com/bnema/go-wayland-bindings/server/primaryselection"
6 "github.com/bnema/go-wayland-bindings/server/wayland"
7 "github.com/bnema/purego-libwayland/server"
8 "golang.org/x/sys/unix"
9)
10
11// The clipboard (wl_data_device) and the primary selection
12// (zwp_primary_selection_v1) each hold one source. Only the client with
13// keyboard focus may set them and sees them; clipboard managers such as
14// cliphist and wl-clipboard use ext_data_control_v1, which sees and sets
15// both without focus. Like wlroots, any client may bind it. Pasting hands the reader's pipe to the source client:
16// the data never goes through neferwl. Drag and drop is not implemented.
17
18// protected is a defensive DISPLAY admission check, not lock ownership.
19func (s *Server) protected() bool {
20 return s.security != nil && s.security.Snapshot().Protected
21}
22
23const (
24 selClipboard = iota
25 selPrimary
26)
27
28// clipSource is a client's offer: its MIME types and how to ask for data.
29type clipSource struct {
30 client server.Client
31 mimes []string
32 send func(mime string, fd int)
33 cancel func()
34 alive func() bool
35 // used: the source was set, rejected or dragged; it cannot be set
36 // again and takes no more MIME types.
37 used bool
38}
39
40// addMime records a MIME type offered before the source is used.
41func (src *clipSource) addMime(mime string) bool {
42 if src.used {
43 return false
44 }
45 src.mimes = append(src.mimes, mime)
46 return true
47}
48
49// clipDevice receives selections for one client.
50type clipDevice struct {
51 client server.Client
52 // control devices (ext_data_control_v1) see every selection.
53 control bool
54 kinds [2]bool
55 alive func() bool
56 // offer announces src (nil clears) as the selection of kind.
57 offer func(kind int, src *clipSource)
58}
59
60func registerClipboard(d *server.Display, s *Server) error {
61 for _, register := range []func() error{
62 func() error {
63 return wayland.NewDataDeviceManagerGlobal(d, 3, func(c server.Client, v, id uint32) {
64 _, _ = wayland.NewDataDeviceManager(c, int32(v), id, dataManager{s})
65 })
66 },
67 func() error {
68 return primaryselection.NewZwpPrimarySelectionDeviceManagerV1Global(d, 1, func(c server.Client, v, id uint32) {
69 _, _ = primaryselection.NewZwpPrimarySelectionDeviceManagerV1(c, int32(v), id, primaryManager{s})
70 })
71 },
72 func() error {
73 return extdatacontrol.NewExtDataControlManagerV1Global(d, 1, func(c server.Client, v, id uint32) {
74 _, _ = extdatacontrol.NewExtDataControlManagerV1(c, int32(v), id, controlManager{s})
75 })
76 },
77 } {
78 if err := register(); err != nil {
79 return err
80 }
81 }
82 return nil
83}
84
85// focusedClient is the client with keyboard focus.
86func (s *Server) focusedClient() (server.Client, bool) {
87 surf, _ := s.focusTarget(s.seat.focused)
88 if surf == nil {
89 return server.Client{}, false
90 }
91 return surf.Client(), true
92}
93
94// setSelection makes src the selection of kind and announces it. A
95// replaced source is cancelled.
96func (s *Server) setSelection(kind int, src *clipSource) {
97 if s.protected() {
98 if src != nil && src != s.selections[kind] && src.alive() {
99 src.cancel()
100 }
101 return
102 }
103 old := s.selections[kind]
104 if old == src {
105 return
106 }
107 s.selections[kind] = src
108 if old != nil && old.alive() {
109 old.cancel()
110 }
111 s.announceAll(kind)
112}
113
114// announceAll sends the selection of kind to the focused client and to
115// clipboard managers.
116func (s *Server) announceAll(kind int) {
117 c, ok := s.focusedClient()
118 s.announce(kind, func(d *clipDevice) bool { return d.control || ok && d.client == c })
119}
120
121// announce sends the selection of kind to the devices matching want.
122func (s *Server) announce(kind int, want func(*clipDevice) bool) {
123 if s.protected() {
124 return
125 }
126 src := s.selections[kind]
127 if src != nil && !src.alive() {
128 src, s.selections[kind] = nil, nil
129 }
130 alive := s.clipDevices[:0]
131 for _, d := range s.clipDevices {
132 if !d.alive() {
133 continue
134 }
135 alive = append(alive, d)
136 if d.kinds[kind] && want(d) {
137 d.offer(kind, src)
138 }
139 }
140 clear(s.clipDevices[len(alive):])
141 s.clipDevices = alive
142}
143
144// focusSelections gives a client that just got keyboard focus the current
145// selections, as the protocol asks before its keyboard enter.
146func (s *Server) focusSelections() {
147 c, ok := s.focusedClient()
148 if !ok {
149 return
150 }
151 for kind := range s.selections {
152 s.announce(kind, func(d *clipDevice) bool { return !d.control && d.client == c })
153 }
154}
155
156// addClipDevice tracks a device and sends it the selections it may see.
157func (s *Server) addClipDevice(d *clipDevice) {
158 s.clipDevices = append(s.clipDevices, d)
159 if s.protected() {
160 return
161 }
162 c, ok := s.focusedClient()
163 if !d.control && (!ok || d.client != c) {
164 return
165 }
166 for kind, src := range s.selections {
167 if d.kinds[kind] {
168 if src != nil && !src.alive() {
169 src = nil
170 }
171 d.offer(kind, src)
172 }
173 }
174}
175
176// sourceGone clears the selections held by a destroyed source.
177func (s *Server) sourceGone(src *clipSource) {
178 for kind := range s.selections {
179 if s.selections[kind] == src {
180 s.selections[kind] = nil
181 s.announceAll(kind)
182 }
183 }
184}
185
186// maySet reports whether a client may set the selection: only the
187// focused one. Any client holding an offer may read it.
188func (s *Server) maySet(c server.Client) bool {
189 f, ok := s.focusedClient()
190 return !s.protected() && ok && f == c
191}
192
193// setFocused sets a selection from a focus-bound device. A used source is
194// ignored (it was already cancelled unless it is the current one); an
195// unfocused client's source is cancelled.
196func (s *Server) setFocused(c server.Client, kind int, src *clipSource) {
197 if src != nil && src.used {
198 return
199 }
200 if src != nil {
201 src.used = true
202 }
203 if !s.maySet(c) {
204 if src != nil {
205 src.cancel()
206 }
207 return
208 }
209 s.setSelection(kind, src)
210}
211
212// receive hands the reader's pipe to the source if it is still the
213// selection; the fd is always closed here (the event dups it). Protection
214// rejects even previously announced offers. Transfers accepted before protection
215// are client-to-client: their already handed-off pipes cannot be revoked here.
216func (s *Server) receive(kind int, src *clipSource, mime string, fd int) {
217 defer unix.Close(fd)
218 if s.protected() || s.selections[kind] != src || !src.alive() {
219 return
220 }
221 for _, m := range src.mimes {
222 if m == mime {
223 src.send(mime, fd)
224 return
225 }
226 }
227}
228
229// wl_data_device_manager
230
231type dataManager struct{ server *Server }
232
233func (m dataManager) CreateDataSource(r *wayland.DataDeviceManager, id uint32) {
234 src := &clipSource{client: r.Client()}
235 res, err := wayland.NewDataSource(r.Client(), r.Version(), id, dataSource{src})
236 if err != nil {
237 return
238 }
239 src.send = func(mime string, fd int) { res.SendSend(mime, fd) }
240 src.cancel = res.SendCancelled
241 src.alive = res.Resource.Alive
242 m.server.dataSources[res.Resource] = src
243 res.OnDestroy = func() {
244 delete(m.server.dataSources, res.Resource)
245 m.server.sourceGone(src)
246 }
247}
248func (m dataManager) GetDataDevice(r *wayland.DataDeviceManager, id uint32, _ *wayland.Seat) {
249 s := m.server
250 res, err := wayland.NewDataDevice(r.Client(), r.Version(), id, dataDevice{s})
251 if err != nil {
252 return
253 }
254 s.addClipDevice(&clipDevice{client: r.Client(), kinds: [2]bool{selClipboard: true}, alive: res.Resource.Alive, offer: func(kind int, src *clipSource) {
255 if src == nil {
256 res.SendSelection(nil)
257 return
258 }
259 offer, err := wayland.NewDataOffer(res.Client(), res.Version(), 0, dataOffer{s, src})
260 if err != nil {
261 return
262 }
263 res.SendDataOffer(offer)
264 for _, mime := range src.mimes {
265 offer.SendOffer(mime)
266 }
267 res.SendSelection(offer)
268 }})
269}
270func (dataManager) Release(r *wayland.DataDeviceManager) { r.Destroy() }
271
272type dataSource struct{ src *clipSource }
273
274func (d dataSource) Offer(_ *wayland.DataSource, mime string) { d.src.addMime(mime) }
275func (dataSource) Destroy(*wayland.DataSource) {}
276func (dataSource) SetActions(*wayland.DataSource, uint32) {}
277
278type dataDevice struct{ server *Server }
279
280// StartDrag is not implemented: the drag is cancelled.
281func (d dataDevice) StartDrag(_ *wayland.DataDevice, src *wayland.DataSource, _, _ *wayland.Surface, _ uint32) {
282 if src == nil {
283 return
284 }
285 cs := d.server.dataSources[src.Resource]
286 if cs == nil || cs.used {
287 // A used source is the selection or already cancelled.
288 return
289 }
290 cs.used = true
291 if src.Resource.Alive() {
292 src.SendCancelled()
293 }
294}
295func (d dataDevice) SetSelection(r *wayland.DataDevice, res *wayland.DataSource, _ uint32) {
296 s := d.server
297 var src *clipSource
298 if res != nil {
299 src = s.dataSources[res.Resource]
300 if src == nil {
301 return
302 }
303 }
304 s.setFocused(r.Client(), selClipboard, src)
305}
306func (dataDevice) Release(r *wayland.DataDevice) { r.Destroy() }
307
308type dataOffer struct {
309 server *Server
310 src *clipSource
311}
312
313func (dataOffer) Accept(*wayland.DataOffer, uint32, string) {}
314func (o dataOffer) Receive(_ *wayland.DataOffer, mime string, fd int) {
315 o.server.receive(selClipboard, o.src, mime, fd)
316}
317func (dataOffer) Destroy(*wayland.DataOffer) {}
318func (dataOffer) Finish(*wayland.DataOffer) {}
319func (dataOffer) SetActions(*wayland.DataOffer, uint32, uint32) {}
320
321// zwp_primary_selection_device_manager_v1
322
323type primaryManager struct{ server *Server }
324
325func (m primaryManager) CreateSource(r *primaryselection.ZwpPrimarySelectionDeviceManagerV1, id uint32) {
326 src := &clipSource{client: r.Client()}
327 res, err := primaryselection.NewZwpPrimarySelectionSourceV1(r.Client(), r.Version(), id, primarySource{src})
328 if err != nil {
329 return
330 }
331 src.send = func(mime string, fd int) { res.SendSend(mime, fd) }
332 src.cancel = res.SendCancelled
333 src.alive = res.Resource.Alive
334 m.server.primarySources[res.Resource] = src
335 res.OnDestroy = func() {
336 delete(m.server.primarySources, res.Resource)
337 m.server.sourceGone(src)
338 }
339}
340func (m primaryManager) GetDevice(r *primaryselection.ZwpPrimarySelectionDeviceManagerV1, id uint32, _ *wayland.Seat) {
341 s := m.server
342 res, err := primaryselection.NewZwpPrimarySelectionDeviceV1(r.Client(), r.Version(), id, primaryDevice{s})
343 if err != nil {
344 return
345 }
346 s.addClipDevice(&clipDevice{client: r.Client(), kinds: [2]bool{selPrimary: true}, alive: res.Resource.Alive, offer: func(kind int, src *clipSource) {
347 if src == nil {
348 res.SendSelection(nil)
349 return
350 }
351 offer, err := primaryselection.NewZwpPrimarySelectionOfferV1(res.Client(), res.Version(), 0, primaryOffer{s, src})
352 if err != nil {
353 return
354 }
355 res.SendDataOffer(offer)
356 for _, mime := range src.mimes {
357 offer.SendOffer(mime)
358 }
359 res.SendSelection(offer)
360 }})
361}
362func (primaryManager) Destroy(*primaryselection.ZwpPrimarySelectionDeviceManagerV1) {}
363
364type primarySource struct{ src *clipSource }
365
366func (p primarySource) Offer(_ *primaryselection.ZwpPrimarySelectionSourceV1, mime string) {
367 p.src.addMime(mime)
368}
369func (primarySource) Destroy(*primaryselection.ZwpPrimarySelectionSourceV1) {}
370
371type primaryDevice struct{ server *Server }
372
373func (d primaryDevice) SetSelection(r *primaryselection.ZwpPrimarySelectionDeviceV1, res *primaryselection.ZwpPrimarySelectionSourceV1, _ uint32) {
374 s := d.server
375 var src *clipSource
376 if res != nil {
377 src = s.primarySources[res.Resource]
378 if src == nil {
379 return
380 }
381 }
382 s.setFocused(r.Client(), selPrimary, src)
383}
384func (primaryDevice) Destroy(*primaryselection.ZwpPrimarySelectionDeviceV1) {}
385
386type primaryOffer struct {
387 server *Server
388 src *clipSource
389}
390
391func (o primaryOffer) Receive(_ *primaryselection.ZwpPrimarySelectionOfferV1, mime string, fd int) {
392 o.server.receive(selPrimary, o.src, mime, fd)
393}
394func (primaryOffer) Destroy(*primaryselection.ZwpPrimarySelectionOfferV1) {}
395
396// ext_data_control_manager_v1
397
398type controlManager struct{ server *Server }
399
400func (m controlManager) CreateDataSource(r *extdatacontrol.ExtDataControlManagerV1, id uint32) {
401 src := &clipSource{client: r.Client()}
402 res, err := extdatacontrol.NewExtDataControlSourceV1(r.Client(), r.Version(), id, controlSource{src})
403 if err != nil {
404 return
405 }
406 src.send = func(mime string, fd int) { res.SendSend(mime, fd) }
407 src.cancel = res.SendCancelled
408 src.alive = res.Resource.Alive
409 m.server.controlSources[res.Resource] = src
410 res.OnDestroy = func() {
411 delete(m.server.controlSources, res.Resource)
412 m.server.sourceGone(src)
413 }
414}
415func (m controlManager) GetDataDevice(r *extdatacontrol.ExtDataControlManagerV1, id uint32, _ *wayland.Seat) {
416 s := m.server
417 res, err := extdatacontrol.NewExtDataControlDeviceV1(r.Client(), r.Version(), id, controlDevice{s})
418 if err != nil {
419 return
420 }
421 s.addClipDevice(&clipDevice{client: r.Client(), control: true, kinds: [2]bool{true, true}, alive: res.Resource.Alive, offer: func(kind int, src *clipSource) {
422 send := res.SendSelection
423 if kind == selPrimary {
424 send = res.SendPrimarySelection
425 }
426 if src == nil {
427 send(nil)
428 return
429 }
430 offer, err := extdatacontrol.NewExtDataControlOfferV1(res.Client(), res.Version(), 0, controlOffer{s, kind, src})
431 if err != nil {
432 return
433 }
434 res.SendDataOffer(offer)
435 for _, mime := range src.mimes {
436 offer.SendOffer(mime)
437 }
438 send(offer)
439 }})
440}
441func (controlManager) Destroy(*extdatacontrol.ExtDataControlManagerV1) {}
442
443type controlSource struct{ src *clipSource }
444
445func (c controlSource) Offer(r *extdatacontrol.ExtDataControlSourceV1, mime string) {
446 if !c.src.addMime(mime) {
447 r.PostError(uint32(extdatacontrol.ExtDataControlSourceV1ErrorInvalidOffer), "offer after the source was used")
448 }
449}
450func (controlSource) Destroy(*extdatacontrol.ExtDataControlSourceV1) {}
451
452type controlDevice struct{ server *Server }
453
454func (d controlDevice) SetSelection(r *extdatacontrol.ExtDataControlDeviceV1, res *extdatacontrol.ExtDataControlSourceV1) {
455 d.set(r, selClipboard, res)
456}
457func (d controlDevice) SetPrimarySelection(r *extdatacontrol.ExtDataControlDeviceV1, res *extdatacontrol.ExtDataControlSourceV1) {
458 d.set(r, selPrimary, res)
459}
460
461// set lets a clipboard manager set a selection without focus. A source
462// may be used once.
463func (d controlDevice) set(r *extdatacontrol.ExtDataControlDeviceV1, kind int, res *extdatacontrol.ExtDataControlSourceV1) {
464 s := d.server
465 var src *clipSource
466 if res != nil {
467 src = s.controlSources[res.Resource]
468 if src == nil {
469 return
470 }
471 if src.used {
472 r.PostError(uint32(extdatacontrol.ExtDataControlDeviceV1ErrorUsedSource), "source already used")
473 return
474 }
475 src.used = true
476 }
477 s.setSelection(kind, src)
478}
479func (controlDevice) Destroy(*extdatacontrol.ExtDataControlDeviceV1) {}
480
481type controlOffer struct {
482 server *Server
483 kind int
484 src *clipSource
485}
486
487func (o controlOffer) Receive(_ *extdatacontrol.ExtDataControlOfferV1, mime string, fd int) {
488 o.server.receive(o.kind, o.src, mime, fd)
489}
490func (controlOffer) Destroy(*extdatacontrol.ExtDataControlOfferV1) {}