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