1//go:build linux
2
3package wayland
4
5import (
6 "sync"
7 "syscall"
8
9 "github.com/bnema/wlturbo/protocol/core"
10 "github.com/bnema/wlturbo/wl"
11 "golang.org/x/sys/unix"
12)
13
14// A cache belongs to exactly one keyboard generation. Dup/replace/close share
15// its FD mutex, not a window-state mutex, so capability loss cannot close an FD
16// while a reader callback duplicates it. Retired callbacks are inert.
17type keyboardMapCache struct {
18 mu sync.Mutex
19 fd int
20 closed bool
21}
22
23func (m *keyboardMapCache) replace(fd int) {
24 m.mu.Lock()
25 defer m.mu.Unlock()
26 if m.closed {
27 _ = syscall.Close(fd)
28 return
29 }
30 if m.fd >= 0 {
31 _ = syscall.Close(m.fd)
32 }
33 m.fd = fd
34}
35func (m *keyboardMapCache) duplicate() (int, error) {
36 m.mu.Lock()
37 defer m.mu.Unlock()
38 if m.closed || m.fd < 0 {
39 return -1, nil
40 }
41 // Close-on-exec: the map must not leak into any child process.
42 return unix.FcntlInt(uintptr(m.fd), unix.F_DUPFD_CLOEXEC, 0)
43}
44func (m *keyboardMapCache) close() {
45 m.mu.Lock()
46 defer m.mu.Unlock()
47 if !m.closed && m.fd >= 0 {
48 _ = syscall.Close(m.fd)
49 }
50 m.fd = -1
51 m.closed = true
52}
53
54func (c *Connection) route(id uint32, ev Event) {
55 if value, ok := c.routes.Load(id); ok {
56 ev.Window = value.(*Window)
57 c.post(ev)
58 } else {
59 CloseEventFD(ev)
60 }
61}
62
63// Focus and frame accumulators below are exclusively reader-owned. They carry
64// surface IDs, never pointers to mutable UI/window state.
65func (w *Window) watchSharedPointer(p *core.Pointer) {
66 c := w.connection
67 w.pointerGen++
68 generation, version := w.pointerGen, w.seatVersion
69 var focus uint32
70 var x, y float64
71 var frame []Input
72 var source uint32
73 flush := func() {
74 if len(frame) > 0 {
75 c.route(focus, Event{Kind: InputFrame, Inputs: frame})
76 frame = nil
77 }
78 source = 0
79 }
80 add := func(in Input) {
81 in.AxisSource = source
82 frame = append(frame, in)
83 if version < 5 {
84 flush()
85 }
86 }
87 p.OnEnter(func(serial, id uint32, px, py wl.Fixed) {
88 flush()
89 focus = id
90 x, y = px.Float64(), py.Float64()
91 add(Input{Kind: "motion", X: x, Y: y, Enter: true, Serial: serial, PointerGen: generation})
92 })
93 p.OnLeave(func(_ uint32, id uint32) {
94 if focus == id {
95 add(Input{Kind: "leave"})
96 flush()
97 focus = 0
98 }
99 })
100 p.OnMotion(func(_ uint32, px, py wl.Fixed) {
101 if focus != 0 {
102 x, y = px.Float64(), py.Float64()
103 add(Input{Kind: "motion", X: x, Y: y})
104 }
105 })
106 p.OnButton(func(serial, _, button, state uint32) {
107 if focus != 0 {
108 kind := "release"
109 if state == 1 {
110 kind = "press"
111 }
112 add(Input{Kind: kind, X: x, Y: y, Button: button, Serial: serial})
113 }
114 })
115 p.OnAxisSource(func(value uint32) { source = value })
116 p.OnAxis(func(_, axis uint32, value wl.Fixed) {
117 if focus != 0 {
118 in := Input{Kind: "axis", X: x, Y: y, Axis: axis}
119 if axis == 0 {
120 in.DY = value.Float64()
121 } else {
122 in.DX = value.Float64()
123 }
124 add(in)
125 }
126 })
127 p.OnAxisValue120(func(axis uint32, value int32) {
128 if focus != 0 {
129 add(Input{Kind: "axis", X: x, Y: y, Axis: axis, Value120: value})
130 }
131 })
132 p.OnAxisStop(func(_, axis uint32) {
133 if focus != 0 {
134 add(Input{Kind: "axis", X: x, Y: y, Axis: axis, AxisStop: true})
135 }
136 })
137 p.OnFrame(flush)
138}
139
140func (w *Window) watchSharedKeyboard(k *core.Keyboard) {
141 c := w.connection
142 var focus uint32
143 cache := &keyboardMapCache{fd: -1}
144 if w.keymapCache != nil {
145 w.keymapCache.close() // one live cache per keyboard generation
146 }
147 w.keymapCache = cache
148 var format, size uint32
149 var mods [4]uint32
150 var rate, delay int32
151 // Keep one reader-owned duplicate to initialize windows created on hotplug.
152 // Its lifetime ends when this keyboard proxy or the connection is released.
153 sendMap := func(id uint32) {
154 fd, err := cache.duplicate()
155 if err != nil {
156 c.post(Event{Kind: TransportError, Err: err})
157 return
158 }
159 if fd >= 0 {
160 c.route(id, Event{Kind: InputKeymap, FD: fd, Format: format, Size: size})
161 }
162 }
163 k.OnKeymap(func(f uint32, fd *wl.OwnedFD, n uint32) {
164 next, err := fd.Take()
165 if err != nil {
166 c.post(Event{Kind: TransportError, Err: err})
167 return
168 }
169 cache.replace(next)
170 format, size = f, n
171 if focus != 0 {
172 sendMap(focus)
173 }
174 })
175 k.OnEnter(func(serial, id uint32, _ []byte) {
176 focus = id
177 sendMap(id)
178 c.route(id, Event{Kind: InputRepeatInfo, Rate: rate, Delay: delay})
179 c.route(id, Event{Kind: InputModifiers, Mods: mods})
180 c.route(id, Event{Kind: InputFocusIn, Serial: serial})
181 })
182 k.OnLeave(func(_, id uint32) {
183 c.route(id, Event{Kind: InputFocusOut})
184 if focus == id {
185 focus = 0
186 }
187 })
188 k.OnKey(func(serial, _, key, state uint32) {
189 if focus != 0 {
190 c.route(focus, Event{Kind: InputKey, Serial: serial, Code: key, Pressed: state == 1})
191 }
192 })
193 k.OnModifiers(func(_, depressed, latched, locked, group uint32) {
194 mods = [4]uint32{depressed, latched, locked, group}
195 if focus != 0 {
196 c.route(focus, Event{Kind: InputModifiers, Mods: mods})
197 }
198 })
199 k.OnRepeatInfo(func(r, d int32) {
200 rate, delay = r, d
201 if focus != 0 {
202 c.route(focus, Event{Kind: InputRepeatInfo, Rate: r, Delay: d})
203 }
204 })
205}