1package wayland
2
3import (
4 "slices"
5 "time"
6
7 "github.com/bnema/go-wayland-bindings/server/inputtimestamps"
8 "github.com/bnema/go-wayland-bindings/server/wayland"
9 "github.com/bnema/purego-libwayland/server"
10)
11
12// zwp_input_timestamps_v1: a client subscribes a wl_keyboard or wl_pointer
13// and gets the device's high-resolution timestamp (libinput microseconds, in
14// the protocol's nanosecond fields) right before each key, motion,
15// button, axis and axis_stop event it sends. Subscriptions are per device
16// resource and die with the device (the object becomes inert) or when the
17// client destroys the timestamps object. There are no touch events, so touch
18// subscriptions only create an inert object.
19//
20// The stamp lists are replaced, never mutated, like the seat device lists.
21
22func registerInputTimestamps(d *server.Display, s *Server) error {
23 return inputtimestamps.NewZwpInputTimestampsManagerV1Global(d, 1, func(c server.Client, v, id uint32) {
24 _, _ = inputtimestamps.NewZwpInputTimestampsManagerV1(c, int32(v), id, timestampsManager{s})
25 })
26}
27
28type timestampsManager struct{ server *Server }
29
30func (timestampsManager) Destroy(*inputtimestamps.ZwpInputTimestampsManagerV1) {}
31
32func (m timestampsManager) GetKeyboardTimestamps(r *inputtimestamps.ZwpInputTimestampsManagerV1, id uint32, kb *wayland.Keyboard) {
33 var dev *server.Resource
34 if kb != nil {
35 dev = kb.Resource
36 }
37 m.subscribe(r, id, dev, m.server.seat.keyStamps)
38}
39
40func (m timestampsManager) GetPointerTimestamps(r *inputtimestamps.ZwpInputTimestampsManagerV1, id uint32, p *wayland.Pointer) {
41 var dev *server.Resource
42 if p != nil {
43 dev = p.Resource
44 }
45 m.subscribe(r, id, dev, m.server.seat.pointerStamps)
46}
47
48// GetTouchTimestamps creates the object only: the seat has no touch events.
49func (timestampsManager) GetTouchTimestamps(r *inputtimestamps.ZwpInputTimestampsManagerV1, id uint32, _ *wayland.Touch) {
50 _, _ = inputtimestamps.NewZwpInputTimestampsV1(r.Client(), r.Version(), id, timestampsHandler{})
51}
52
53// subscribe creates the timestamps object for dev and records it in stamps.
54// A missing or dead device leaves the object inert.
55func (m timestampsManager) subscribe(r *inputtimestamps.ZwpInputTimestampsManagerV1, id uint32, dev *server.Resource, stamps map[*server.Resource][]*inputtimestamps.ZwpInputTimestampsV1) {
56 ts, err := inputtimestamps.NewZwpInputTimestampsV1(r.Client(), r.Version(), id, timestampsHandler{})
57 if err != nil || dev == nil || !dev.Alive() {
58 return
59 }
60 stamps[dev] = append(slices.Clone(stamps[dev]), ts)
61 ts.OnDestroy = func() {
62 // The device may have died first and dropped its entry.
63 list, ok := stamps[dev]
64 if !ok {
65 return
66 }
67 list = slices.DeleteFunc(slices.Clone(list), func(x *inputtimestamps.ZwpInputTimestampsV1) bool { return x == ts })
68 if len(list) == 0 {
69 delete(stamps, dev)
70 } else {
71 stamps[dev] = list
72 }
73 }
74}
75
76type timestampsHandler struct{}
77
78func (timestampsHandler) Destroy(*inputtimestamps.ZwpInputTimestampsV1) {}
79
80// stampKeyboard sends t to the subscribers of k; call it right before a
81// wl_keyboard.key. It allocates nothing.
82func (s *Server) stampKeyboard(k *wayland.Keyboard, t time.Duration) {
83 sendStamps(s.seat.keyStamps[k.Resource], t)
84}
85
86// stampPointer sends t to the subscribers of p; call it right before a
87// wl_pointer motion, button, axis or axis_stop.
88func (s *Server) stampPointer(p *wayland.Pointer, t time.Duration) {
89 sendStamps(s.seat.pointerStamps[p.Resource], t)
90}
91
92func sendStamps(list []*inputtimestamps.ZwpInputTimestampsV1, t time.Duration) {
93 if len(list) == 0 {
94 return
95 }
96 sec := uint64(t / time.Second)
97 ns := uint32(t % time.Second)
98 for _, ts := range list {
99 ts.SendTimestamp(uint32(sec>>32), uint32(sec), ns)
100 }
101}