1package wayland
2
3import (
4 "bytes"
5 "time"
6
7 "github.com/bnema/go-wayland-bindings/server/virtualkeyboard"
8 "github.com/bnema/go-wayland-bindings/server/wayland"
9 "github.com/bnema/neferwl/internal/ports"
10 "github.com/bnema/purego-libwayland/server"
11 "golang.org/x/sys/unix"
12)
13
14// maxVirtualKeymap bounds a keymap read from a virtual keyboard client.
15const maxVirtualKeymap = 1 << 20
16
17// virtualKeyboard is a zwp_virtual_keyboard_v1 (wtype, dictation tools).
18// Its keys go to the focused client. The focused client's keyboards carry
19// the keymap of whichever keyboard typed last: a virtual key switches them
20// to the virtual keymap, the next real key or focus change switches back.
21type virtualKeyboard struct {
22 server *Server
23 fd int // sealed copy of the client keymap, -1 before one
24 size uint32
25 text string // the keymap, compared with the active one
26 mods ports.ModState // last modifiers request, zero before one
27 // pressed are keys held on focus; a focus change releases them by leave.
28 pressed map[uint32]bool
29 focus ports.WindowID
30 generation ports.LockGeneration // epoch of held keys; never release across protection
31 dropReleases bool // after an epoch change, ignore releases without a new press
32}
33
34// registerVirtualKeyboard exposes the global to every client, as wlroots
35// does by default: any local client may type into the focused window.
36func registerVirtualKeyboard(d *server.Display, s *Server) error {
37 return virtualkeyboard.NewZwpVirtualKeyboardManagerV1Global(d, 1, func(c server.Client, v, id uint32) {
38 _, _ = virtualkeyboard.NewZwpVirtualKeyboardManagerV1(c, int32(v), id, virtualKeyboardManager{s})
39 })
40}
41
42type virtualKeyboardManager struct{ server *Server }
43
44func (m virtualKeyboardManager) CreateVirtualKeyboard(r *virtualkeyboard.ZwpVirtualKeyboardManagerV1, _ *wayland.Seat, id uint32) {
45 k := &virtualKeyboard{server: m.server, fd: -1, pressed: map[uint32]bool{}}
46 res, err := virtualkeyboard.NewZwpVirtualKeyboardV1(r.Client(), r.Version(), id, k)
47 if err != nil {
48 return
49 }
50 res.OnDestroy = k.release
51 m.server.log.Info().Int("pid", r.Client().PID()).Msg("virtual keyboard created")
52}
53
54// Keymap copies the client keymap into a sealed memfd: other clients never
55// see the client's fd. An unreadable or malformed keymap is ignored.
56func (k *virtualKeyboard) Keymap(_ *virtualkeyboard.ZwpVirtualKeyboardV1, format uint32, fd int, size uint32) {
57 if k.server.protected() {
58 unix.Close(fd)
59 k.admitted() // discard held state and ownership without delivery
60 if k.fd >= 0 {
61 unix.Close(k.fd)
62 }
63 k.fd, k.size, k.text = -1, 0, ""
64 // A rejected replacement must not leave an old layout usable after
65 // unlock. The client must send a fresh keymap before typing again.
66 return
67 }
68 text, ok := readKeymap(fd, size)
69 unix.Close(fd)
70 if format != uint32(wayland.KeyboardKeymapFormatXkbV1) || !ok {
71 k.server.log.Debug().Msg("virtual keymap rejected")
72 return
73 }
74 if wide, from := widenKeycodes(text); from != 0 {
75 k.server.log.Debug().Int("maximum", from).Int("widened", minKeycodeMax).Msg("virtual keymap keycode range widened")
76 text = wide
77 }
78 copyFD, copySize, err := keymapFile(text)
79 if err != nil {
80 k.server.log.Warn().Err(err).Msg("virtual keymap copy failed")
81 return
82 }
83 if k.fd >= 0 {
84 unix.Close(k.fd)
85 }
86 k.fd, k.size = copyFD, copySize
87 old := k.server.activeKeymapText()
88 k.text = text
89 if s := k.server; s.seat.keymapOwner == k && text != old {
90 // Clients must see the new layout before the next key.
91 s.sendKeymapAll(k.fd, k.size)
92 }
93}
94
95// readKeymap reads a NUL-terminated xkb keymap of size bytes from a
96// regular file.
97func readKeymap(fd int, size uint32) (string, bool) {
98 var st unix.Stat_t
99 if size == 0 || size > maxVirtualKeymap || unix.Fstat(fd, &st) != nil || st.Mode&unix.S_IFMT != unix.S_IFREG || st.Size < int64(size) {
100 return "", false
101 }
102 data := make([]byte, size)
103 if n, err := unix.Pread(fd, data, 0); err != nil || n != int(size) {
104 return "", false
105 }
106 if i := bytes.IndexByte(data, 0); i >= 0 {
107 data = data[:i]
108 }
109 if !bytes.Contains(data, []byte("xkb_keymap")) {
110 return "", false
111 }
112 return string(data), true
113}
114
115// keyboards switches the focused client to k's keymap and returns its
116// keyboards, or nil after a no_keymap error or without focus.
117func (k *virtualKeyboard) keyboards(r *virtualkeyboard.ZwpVirtualKeyboardV1) []*wayland.Keyboard {
118 if !k.admitted() {
119 return nil
120 }
121 if k.fd < 0 {
122 r.PostError(uint32(virtualkeyboard.ZwpVirtualKeyboardV1ErrorNoKeymap), "no keymap")
123 return nil
124 }
125 s := k.server
126 _, keyboards := s.focusTarget(s.seat.focused)
127 if len(keyboards) == 0 {
128 return nil
129 }
130 if k.focus != s.seat.focused {
131 clear(k.pressed)
132 k.focus = s.seat.focused
133 }
134 s.useKeymap(k)
135 s.serial++
136 return keyboards
137}
138
139func (k *virtualKeyboard) Key(r *virtualkeyboard.ZwpVirtualKeyboardV1, time, key, state uint32) {
140 if !k.admitted() || state != 1 && k.dropReleases && !k.pressed[key] {
141 return
142 }
143 keyboards := k.keyboards(r)
144 if keyboards == nil {
145 return
146 }
147 if state == 1 {
148 k.pressed[key] = true
149 } else {
150 delete(k.pressed, key)
151 }
152 for _, kb := range keyboards {
153 kb.SendKey(k.server.serial, time, key, state)
154 }
155}
156
157func (k *virtualKeyboard) Modifiers(r *virtualkeyboard.ZwpVirtualKeyboardV1, depressed, latched, locked, group uint32) {
158 if !k.admitted() {
159 return
160 }
161 k.mods = ports.ModState{Depressed: depressed, Latched: latched, Locked: locked, Group: group}
162 for _, kb := range k.keyboards(r) {
163 kb.SendModifiers(k.server.serial, depressed, latched, locked, group)
164 }
165}
166
167// admitted forgets old held state even if protection began and ended between
168// this keyboard's requests. Snapshot is coherent; DISPLAY owns admission.
169func (k *virtualKeyboard) admitted() bool {
170 state := ports.SecurityState{}
171 if k.server.security != nil {
172 state = k.server.security.Snapshot()
173 }
174 if state.Protected || state.Generation != k.generation {
175 clear(k.pressed)
176 k.mods = ports.ModState{}
177 k.focus = 0
178 k.generation = state.Generation
179 k.dropReleases = true
180 if k.server.seat.keymapOwner == k {
181 // Forget ownership silently; the parent resets focus and restores
182 // the seat layout at the lock transition.
183 k.server.seat.keymapOwner = nil
184 }
185 }
186 return !state.Protected
187}
188
189func (*virtualKeyboard) Destroy(*virtualkeyboard.ZwpVirtualKeyboardV1) {}
190
191// release runs when the resource dies, by request or client disconnect:
192// keys still held on the focused client are released.
193func (k *virtualKeyboard) release() {
194 s := k.server
195 generation := k.generation
196 admitted := k.admitted() && generation == k.generation
197 if admitted && len(k.pressed) > 0 && k.focus == s.seat.focused {
198 _, keyboards := s.focusTarget(s.seat.focused)
199 var ts unix.Timespec
200 _ = unix.ClockGettime(unix.CLOCK_MONOTONIC, &ts)
201 // Input events carry CLOCK_MONOTONIC milliseconds (libinput's clock).
202 now := uint32(ts.Nano() / int64(time.Millisecond))
203 for key := range k.pressed {
204 s.serial++
205 for _, kb := range keyboards {
206 kb.SendKey(s.serial, now, key, 0)
207 }
208 }
209 }
210 if s.seat.keymapOwner == k {
211 if admitted {
212 s.useKeymap(nil)
213 } else {
214 // Drop ownership without delivering keymap/modifiers into the locker.
215 s.seat.keymapOwner = nil
216 }
217 }
218 if k.fd >= 0 {
219 unix.Close(k.fd)
220 k.fd = -1
221 }
222}
223
224// useKeymap makes every keyboard carry the keymap of k, or the seat keymap
225// when k is nil. Switching back restores the seat modifiers too.
226func (s *Server) useKeymap(k *virtualKeyboard) {
227 if s.seat.keymapOwner == k {
228 return
229 }
230 old := s.activeKeymapText()
231 s.seat.keymapOwner = k
232 // Like wlroots and smithay, a keymap equal to the active one is not
233 // sent again: tools such as dictation apps create a virtual keyboard
234 // with the seat layout per use, and each resend makes every client
235 // (Xwayland included) recompile its keymap.
236 if s.activeKeymapText() != old {
237 s.sendKeymapAll(s.currentKeymap())
238 }
239 // Modifiers follow the keyboard that types: a skipped keymap no longer
240 // resets them, so a virtual keyboard never types through seat Shift or
241 // Caps Lock, and the seat gets its own back.
242 m := s.seat.modState
243 if k != nil {
244 m = k.mods
245 }
246 s.serial++
247 for _, kb := range s.clientKeyboards(s.focusClient()) {
248 kb.SendModifiers(s.serial, m.Depressed, m.Latched, m.Locked, m.Group)
249 }
250 s.log.Debug().Bool("virtual", k != nil).Msg("keymap switched")
251}
252
253func (s *Server) sendKeymapAll(fd int, size uint32) {
254 for _, list := range s.seat.keyboards {
255 for _, kb := range list {
256 if kb.Resource.Alive() {
257 kb.SendKeymap(uint32(wayland.KeyboardKeymapFormatXkbV1), fd, size)
258 }
259 }
260 }
261}
262
263// activeKeymapText is the text of the keymap keyboards carry.
264func (s *Server) activeKeymapText() string {
265 if k := s.seat.keymapOwner; k != nil {
266 return k.text
267 }
268 return s.seat.keymapText
269}
270
271// currentKeymap is the keymap a new keyboard must start with.
272func (s *Server) currentKeymap() (int, uint32) {
273 if k := s.seat.keymapOwner; k != nil {
274 return k.fd, k.size
275 }
276 return s.seat.keymapFD, s.seat.keymapSize
277}