merge: vk-keymap (virtual keymap dedupe, relative motion deltas)
5 files · +136 -16
M internal/adapters/wayland/commands_test.go +5 -4
| @@ -12,16 +12,17 @@ import ( | |||
| 12 | 12 | ||
| 13 | 13 | func TestDrainCommandsCollapsesMotion(t *testing.T) { | |
| 14 | 14 | cmds := make(chan ports.ClientCommand, 16) | |
| 15 | - | cmds <- ports.PointerMotionTo{ID: 1, X: 2} | |
| 16 | - | cmds <- ports.PointerMotionTo{ID: 1, X: 3} | |
| 15 | + | cmds <- ports.PointerMotionTo{ID: 1, X: 2, DX: 0.25, UnaccelDX: 0.5} | |
| 16 | + | cmds <- ports.PointerMotionTo{ID: 1, X: 3, DX: 0.25, DY: -0.5, UnaccelDX: 0.5, UnaccelDY: -1} | |
| 17 | 17 | cmds <- ports.PointerButtonTo{ID: 1, Button: 272, Pressed: true} | |
| 18 | 18 | cmds <- ports.PointerMotionTo{ID: 1, X: 4} | |
| 19 | 19 | cmds <- ports.PointerFocus{ID: 2} | |
| 20 | 20 | cmds <- ports.PointerMotionTo{ID: 2, X: 5} | |
| 21 | 21 | cmds <- ports.PointerMotionTo{ID: 2, X: 6} | |
| 22 | - | batch, open := drainCommands(ports.PointerMotionTo{ID: 1, X: 1}, cmds) | |
| 22 | + | batch, open := drainCommands(ports.PointerMotionTo{ID: 1, X: 1, DX: 0.25, UnaccelDX: 0.5}, cmds) | |
| 23 | 23 | want := []ports.ClientCommand{ | |
| 24 | - | ports.PointerMotionTo{ID: 1, X: 3}, | |
| 24 | + | // Slow motions add up: none of the sub-pixel deltas is lost. | |
| 25 | + | ports.PointerMotionTo{ID: 1, X: 3, DX: 0.75, DY: -0.5, UnaccelDX: 1.5, UnaccelDY: -1}, | |
| 25 | 26 | ports.PointerButtonTo{ID: 1, Button: 272, Pressed: true}, | |
| 26 | 27 | ports.PointerMotionTo{ID: 1, X: 4}, | |
| 27 | 28 | ports.PointerFocus{ID: 2}, | |
| @@ -12,16 +12,17 @@ import ( | |||||
| 12 | 12 | ||||
| 13 | func TestDrainCommandsCollapsesMotion(t *testing.T) { | 13 | func TestDrainCommandsCollapsesMotion(t *testing.T) { | ||
| 14 | cmds := make(chan ports.ClientCommand, 16) | 14 | cmds := make(chan ports.ClientCommand, 16) | ||
| 15 | - | cmds <- ports.PointerMotionTo{ID: 1, X: 2} | 15 | + | cmds <- ports.PointerMotionTo{ID: 1, X: 2, DX: 0.25, UnaccelDX: 0.5} |
| 16 | - | cmds <- ports.PointerMotionTo{ID: 1, X: 3} | 16 | + | cmds <- ports.PointerMotionTo{ID: 1, X: 3, DX: 0.25, DY: -0.5, UnaccelDX: 0.5, UnaccelDY: -1} |
| 17 | cmds <- ports.PointerButtonTo{ID: 1, Button: 272, Pressed: true} | 17 | cmds <- ports.PointerButtonTo{ID: 1, Button: 272, Pressed: true} | ||
| 18 | cmds <- ports.PointerMotionTo{ID: 1, X: 4} | 18 | cmds <- ports.PointerMotionTo{ID: 1, X: 4} | ||
| 19 | cmds <- ports.PointerFocus{ID: 2} | 19 | cmds <- ports.PointerFocus{ID: 2} | ||
| 20 | cmds <- ports.PointerMotionTo{ID: 2, X: 5} | 20 | cmds <- ports.PointerMotionTo{ID: 2, X: 5} | ||
| 21 | cmds <- ports.PointerMotionTo{ID: 2, X: 6} | 21 | cmds <- ports.PointerMotionTo{ID: 2, X: 6} | ||
| 22 | - | batch, open := drainCommands(ports.PointerMotionTo{ID: 1, X: 1}, cmds) | 22 | + | batch, open := drainCommands(ports.PointerMotionTo{ID: 1, X: 1, DX: 0.25, UnaccelDX: 0.5}, cmds) |
| 23 | want := []ports.ClientCommand{ | 23 | want := []ports.ClientCommand{ | ||
| 24 | - | ports.PointerMotionTo{ID: 1, X: 3}, | 24 | + | // Slow motions add up: none of the sub-pixel deltas is lost. |
| 25 | + | ports.PointerMotionTo{ID: 1, X: 3, DX: 0.75, DY: -0.5, UnaccelDX: 1.5, UnaccelDY: -1}, | |||
| 25 | ports.PointerButtonTo{ID: 1, Button: 272, Pressed: true}, | 26 | ports.PointerButtonTo{ID: 1, Button: 272, Pressed: true}, | ||
| 26 | ports.PointerMotionTo{ID: 1, X: 4}, | 27 | ports.PointerMotionTo{ID: 1, X: 4}, | ||
| 27 | ports.PointerFocus{ID: 2}, | 28 | ports.PointerFocus{ID: 2}, | ||
M internal/adapters/wayland/keyboard_test.go +8 -0
| @@ -129,10 +129,18 @@ type eventsProxy struct { | |||
| 129 | 129 | wlturbo.BaseProxy | |
| 130 | 130 | opcodes []uint16 | |
| 131 | 131 | keymaps []string | |
| 132 | + | locked []uint32 // locked modifiers of each modifiers event | |
| 132 | 133 | } | |
| 133 | 134 | ||
| 134 | 135 | func (p *eventsProxy) Dispatch(e *wlturbo.Event) { | |
| 135 | 136 | p.opcodes = append(p.opcodes, e.Opcode) | |
| 137 | + | if e.Opcode == uint16(wayland.KeyboardEventModifiers) { | |
| 138 | + | e.Uint32() // serial | |
| 139 | + | e.Uint32() // depressed | |
| 140 | + | e.Uint32() // latched | |
| 141 | + | p.locked = append(p.locked, e.Uint32()) | |
| 142 | + | return | |
| 143 | + | } | |
| 136 | 144 | if e.Opcode != uint16(wayland.KeyboardEventKeymap) { | |
| 137 | 145 | return | |
| 138 | 146 | } | |
| @@ -129,10 +129,18 @@ type eventsProxy struct { | |||||
| 129 | wlturbo.BaseProxy | 129 | wlturbo.BaseProxy | ||
| 130 | opcodes []uint16 | 130 | opcodes []uint16 | ||
| 131 | keymaps []string | 131 | keymaps []string | ||
| 132 | + | locked []uint32 // locked modifiers of each modifiers event | |||
| 132 | } | 133 | } | ||
| 133 | 134 | ||||
| 134 | func (p *eventsProxy) Dispatch(e *wlturbo.Event) { | 135 | func (p *eventsProxy) Dispatch(e *wlturbo.Event) { | ||
| 135 | p.opcodes = append(p.opcodes, e.Opcode) | 136 | p.opcodes = append(p.opcodes, e.Opcode) | ||
| 137 | + | if e.Opcode == uint16(wayland.KeyboardEventModifiers) { | |||
| 138 | + | e.Uint32() // serial | |||
| 139 | + | e.Uint32() // depressed | |||
| 140 | + | e.Uint32() // latched | |||
| 141 | + | p.locked = append(p.locked, e.Uint32()) | |||
| 142 | + | return | |||
| 143 | + | } | |||
| 136 | if e.Opcode != uint16(wayland.KeyboardEventKeymap) { | 144 | if e.Opcode != uint16(wayland.KeyboardEventKeymap) { | ||
| 137 | return | 145 | return | ||
| 138 | } | 146 | } | ||
M internal/adapters/wayland/server.go +11 -3
| @@ -101,6 +101,7 @@ type Server struct { | |||
| 101 | 101 | heldKeys map[uint32]bool | |
| 102 | 102 | keymapFD int | |
| 103 | 103 | keymapSize uint32 | |
| 104 | + | keymapText string // the seat keymap, to compare virtual keymaps with | |
| 104 | 105 | keymapOwner *virtualKeyboard // nil: keyboards carry the seat keymap | |
| 105 | 106 | repeatRate, repeatDelay int | |
| 106 | 107 | // eventMu protects only the notification queue, not display-owned window state. | |
| @@ -216,7 +217,7 @@ func New(opts Options, ch Channels, log zerowrap.Logger) (*Server, error) { | |||
| 216 | 217 | d.Close() | |
| 217 | 218 | return nil, e | |
| 218 | 219 | } | |
| 219 | - | s.keymapFD, s.keymapSize = fd, size | |
| 220 | + | s.keymapFD, s.keymapSize, s.keymapText = fd, size, opts.Keymap | |
| 220 | 221 | } | |
| 221 | 222 | s.cleanup = func() { | |
| 222 | 223 | if s.keymapFD >= 0 { | |
| @@ -431,7 +432,8 @@ func (s *Server) forwardContents(ctx context.Context) { | |||
| 431 | 432 | const maxCommandBatch = 256 | |
| 432 | 433 | ||
| 433 | 434 | // drainCommands returns first plus the commands already queued, with runs of | |
| 434 | - | // pointer motion for the same window collapsed into the latest one. open is | |
| 435 | + | // pointer motion for the same window collapsed into the latest one, their | |
| 436 | + | // relative deltas summed. open is | |
| 435 | 437 | // false once the channel is closed. | |
| 436 | 438 | func drainCommands(first ports.ClientCommand, cmds <-chan ports.ClientCommand) (batch []ports.ClientCommand, open bool) { | |
| 437 | 439 | batch = append(batch, first) | |
| @@ -443,6 +445,12 @@ func drainCommands(first ports.ClientCommand, cmds <-chan ports.ClientCommand) ( | |||
| 443 | 445 | } | |
| 444 | 446 | if m, isMotion := c.(ports.PointerMotionTo); isMotion { | |
| 445 | 447 | if last, lastMotion := batch[len(batch)-1].(ports.PointerMotionTo); lastMotion && last.ID == m.ID { | |
| 448 | + | // The position is the latest; relative deltas add up so | |
| 449 | + | // locked pointers (games) lose no movement. | |
| 450 | + | m.DX += last.DX | |
| 451 | + | m.DY += last.DY | |
| 452 | + | m.UnaccelDX += last.UnaccelDX | |
| 453 | + | m.UnaccelDY += last.UnaccelDY | |
| 446 | 454 | batch[len(batch)-1] = m | |
| 447 | 455 | continue | |
| 448 | 456 | } | |
| @@ -702,7 +710,7 @@ func (s *Server) setKeymap(c ports.SetKeymap) { | |||
| 702 | 710 | if s.keymapFD >= 0 { | |
| 703 | 711 | unix.Close(s.keymapFD) | |
| 704 | 712 | } | |
| 705 | - | s.keymapFD, s.keymapSize = fd, size | |
| 713 | + | s.keymapFD, s.keymapSize, s.keymapText = fd, size, c.Keymap | |
| 706 | 714 | s.keymapOwner = nil | |
| 707 | 715 | focused := s.focused | |
| 708 | 716 | s.changeFocus(0) | |
| @@ -101,6 +101,7 @@ type Server struct { | |||||
| 101 | heldKeys map[uint32]bool | 101 | heldKeys map[uint32]bool | ||
| 102 | keymapFD int | 102 | keymapFD int | ||
| 103 | keymapSize uint32 | 103 | keymapSize uint32 | ||
| 104 | + | keymapText string // the seat keymap, to compare virtual keymaps with | |||
| 104 | keymapOwner *virtualKeyboard // nil: keyboards carry the seat keymap | 105 | keymapOwner *virtualKeyboard // nil: keyboards carry the seat keymap | ||
| 105 | repeatRate, repeatDelay int | 106 | repeatRate, repeatDelay int | ||
| 106 | // eventMu protects only the notification queue, not display-owned window state. | 107 | // eventMu protects only the notification queue, not display-owned window state. | ||
| @@ -216,7 +217,7 @@ func New(opts Options, ch Channels, log zerowrap.Logger) (*Server, error) { | |||||
| 216 | d.Close() | 217 | d.Close() | ||
| 217 | return nil, e | 218 | return nil, e | ||
| 218 | } | 219 | } | ||
| 219 | - | s.keymapFD, s.keymapSize = fd, size | 220 | + | s.keymapFD, s.keymapSize, s.keymapText = fd, size, opts.Keymap |
| 220 | } | 221 | } | ||
| 221 | s.cleanup = func() { | 222 | s.cleanup = func() { | ||
| 222 | if s.keymapFD >= 0 { | 223 | if s.keymapFD >= 0 { | ||
| @@ -431,7 +432,8 @@ func (s *Server) forwardContents(ctx context.Context) { | |||||
| 431 | const maxCommandBatch = 256 | 432 | const maxCommandBatch = 256 | ||
| 432 | 433 | ||||
| 433 | // drainCommands returns first plus the commands already queued, with runs of | 434 | // drainCommands returns first plus the commands already queued, with runs of | ||
| 434 | - | // pointer motion for the same window collapsed into the latest one. open is | 435 | + | // pointer motion for the same window collapsed into the latest one, their |
| 436 | + | // relative deltas summed. open is | |||
| 435 | // false once the channel is closed. | 437 | // false once the channel is closed. | ||
| 436 | func drainCommands(first ports.ClientCommand, cmds <-chan ports.ClientCommand) (batch []ports.ClientCommand, open bool) { | 438 | func drainCommands(first ports.ClientCommand, cmds <-chan ports.ClientCommand) (batch []ports.ClientCommand, open bool) { | ||
| 437 | batch = append(batch, first) | 439 | batch = append(batch, first) | ||
| @@ -443,6 +445,12 @@ func drainCommands(first ports.ClientCommand, cmds <-chan ports.ClientCommand) ( | |||||
| 443 | } | 445 | } | ||
| 444 | if m, isMotion := c.(ports.PointerMotionTo); isMotion { | 446 | if m, isMotion := c.(ports.PointerMotionTo); isMotion { | ||
| 445 | if last, lastMotion := batch[len(batch)-1].(ports.PointerMotionTo); lastMotion && last.ID == m.ID { | 447 | if last, lastMotion := batch[len(batch)-1].(ports.PointerMotionTo); lastMotion && last.ID == m.ID { | ||
| 448 | + | // The position is the latest; relative deltas add up so | |||
| 449 | + | // locked pointers (games) lose no movement. | |||
| 450 | + | m.DX += last.DX | |||
| 451 | + | m.DY += last.DY | |||
| 452 | + | m.UnaccelDX += last.UnaccelDX | |||
| 453 | + | m.UnaccelDY += last.UnaccelDY | |||
| 446 | batch[len(batch)-1] = m | 454 | batch[len(batch)-1] = m | ||
| 447 | continue | 455 | continue | ||
| 448 | } | 456 | } | ||
| @@ -702,7 +710,7 @@ func (s *Server) setKeymap(c ports.SetKeymap) { | |||||
| 702 | if s.keymapFD >= 0 { | 710 | if s.keymapFD >= 0 { | ||
| 703 | unix.Close(s.keymapFD) | 711 | unix.Close(s.keymapFD) | ||
| 704 | } | 712 | } | ||
| 705 | - | s.keymapFD, s.keymapSize = fd, size | 713 | + | s.keymapFD, s.keymapSize, s.keymapText = fd, size, c.Keymap |
| 706 | s.keymapOwner = nil | 714 | s.keymapOwner = nil | ||
| 707 | focused := s.focused | 715 | focused := s.focused | ||
| 708 | s.changeFocus(0) | 716 | s.changeFocus(0) | ||
M internal/adapters/wayland/virtualkeyboard.go +32 -7
| @@ -22,6 +22,8 @@ type virtualKeyboard struct { | |||
| 22 | 22 | server *Server | |
| 23 | 23 | fd int // sealed copy of the client keymap, -1 before one | |
| 24 | 24 | size uint32 | |
| 25 | + | text string // the keymap, compared with the active one | |
| 26 | + | mods ports.ModState // last modifiers request, zero before one | |
| 25 | 27 | // pressed are keys held on focus; a focus change releases them by leave. | |
| 26 | 28 | pressed map[uint32]bool | |
| 27 | 29 | focus ports.WindowID | |
| @@ -65,7 +67,9 @@ func (k *virtualKeyboard) Keymap(_ *virtualkeyboard.ZwpVirtualKeyboardV1, format | |||
| 65 | 67 | unix.Close(k.fd) | |
| 66 | 68 | } | |
| 67 | 69 | k.fd, k.size = copyFD, copySize | |
| 68 | - | if s := k.server; s.keymapOwner == k { | |
| 70 | + | old := k.server.activeKeymapText() | |
| 71 | + | k.text = text | |
| 72 | + | if s := k.server; s.keymapOwner == k && text != old { | |
| 69 | 73 | // Clients must see the new layout before the next key. | |
| 70 | 74 | s.sendKeymapAll(k.fd, k.size) | |
| 71 | 75 | } | |
| @@ -128,6 +132,7 @@ func (k *virtualKeyboard) Key(r *virtualkeyboard.ZwpVirtualKeyboardV1, time, key | |||
| 128 | 132 | } | |
| 129 | 133 | ||
| 130 | 134 | func (k *virtualKeyboard) Modifiers(r *virtualkeyboard.ZwpVirtualKeyboardV1, depressed, latched, locked, group uint32) { | |
| 135 | + | k.mods = ports.ModState{Depressed: depressed, Latched: latched, Locked: locked, Group: group} | |
| 131 | 136 | for _, kb := range k.keyboards(r) { | |
| 132 | 137 | kb.SendModifiers(k.server.serial, depressed, latched, locked, group) | |
| 133 | 138 | } | |
| @@ -164,13 +169,25 @@ func (s *Server) useKeymap(k *virtualKeyboard) { | |||
| 164 | 169 | if s.keymapOwner == k { | |
| 165 | 170 | return | |
| 166 | 171 | } | |
| 172 | + | old := s.activeKeymapText() | |
| 167 | 173 | s.keymapOwner = k | |
| 168 | - | s.sendKeymapAll(s.currentKeymap()) | |
| 169 | - | if k == nil { | |
| 170 | - | s.serial++ | |
| 171 | - | for _, kb := range s.clientKeyboards(s.focusClient()) { | |
| 172 | - | s.sendModifiers(kb) | |
| 173 | - | } | |
| 174 | + | // Like wlroots and smithay, a keymap equal to the active one is not | |
| 175 | + | // sent again: tools such as dictation apps create a virtual keyboard | |
| 176 | + | // with the seat layout per use, and each resend makes every client | |
| 177 | + | // (Xwayland included) recompile its keymap. | |
| 178 | + | if s.activeKeymapText() != old { | |
| 179 | + | s.sendKeymapAll(s.currentKeymap()) | |
| 180 | + | } | |
| 181 | + | // Modifiers follow the keyboard that types: a skipped keymap no longer | |
| 182 | + | // resets them, so a virtual keyboard never types through seat Shift or | |
| 183 | + | // Caps Lock, and the seat gets its own back. | |
| 184 | + | m := s.modState | |
| 185 | + | if k != nil { | |
| 186 | + | m = k.mods | |
| 187 | + | } | |
| 188 | + | s.serial++ | |
| 189 | + | for _, kb := range s.clientKeyboards(s.focusClient()) { | |
| 190 | + | kb.SendModifiers(s.serial, m.Depressed, m.Latched, m.Locked, m.Group) | |
| 174 | 191 | } | |
| 175 | 192 | s.log.Debug().Bool("virtual", k != nil).Msg("keymap switched") | |
| 176 | 193 | } | |
| @@ -185,6 +202,14 @@ func (s *Server) sendKeymapAll(fd int, size uint32) { | |||
| 185 | 202 | } | |
| 186 | 203 | } | |
| 187 | 204 | ||
| 205 | + | // activeKeymapText is the text of the keymap keyboards carry. | |
| 206 | + | func (s *Server) activeKeymapText() string { | |
| 207 | + | if k := s.keymapOwner; k != nil { | |
| 208 | + | return k.text | |
| 209 | + | } | |
| 210 | + | return s.keymapText | |
| 211 | + | } | |
| 212 | + | ||
| 188 | 213 | // currentKeymap is the keymap a new keyboard must start with. | |
| 189 | 214 | func (s *Server) currentKeymap() (int, uint32) { | |
| 190 | 215 | if k := s.keymapOwner; k != nil { | |
| @@ -22,6 +22,8 @@ type virtualKeyboard struct { | |||||
| 22 | server *Server | 22 | server *Server | ||
| 23 | fd int // sealed copy of the client keymap, -1 before one | 23 | fd int // sealed copy of the client keymap, -1 before one | ||
| 24 | size uint32 | 24 | size uint32 | ||
| 25 | + | text string // the keymap, compared with the active one | |||
| 26 | + | mods ports.ModState // last modifiers request, zero before one | |||
| 25 | // pressed are keys held on focus; a focus change releases them by leave. | 27 | // pressed are keys held on focus; a focus change releases them by leave. | ||
| 26 | pressed map[uint32]bool | 28 | pressed map[uint32]bool | ||
| 27 | focus ports.WindowID | 29 | focus ports.WindowID | ||
| @@ -65,7 +67,9 @@ func (k *virtualKeyboard) Keymap(_ *virtualkeyboard.ZwpVirtualKeyboardV1, format | |||||
| 65 | unix.Close(k.fd) | 67 | unix.Close(k.fd) | ||
| 66 | } | 68 | } | ||
| 67 | k.fd, k.size = copyFD, copySize | 69 | k.fd, k.size = copyFD, copySize | ||
| 68 | - | if s := k.server; s.keymapOwner == k { | 70 | + | old := k.server.activeKeymapText() |
| 71 | + | k.text = text | |||
| 72 | + | if s := k.server; s.keymapOwner == k && text != old { | |||
| 69 | // Clients must see the new layout before the next key. | 73 | // Clients must see the new layout before the next key. | ||
| 70 | s.sendKeymapAll(k.fd, k.size) | 74 | s.sendKeymapAll(k.fd, k.size) | ||
| 71 | } | 75 | } | ||
| @@ -128,6 +132,7 @@ func (k *virtualKeyboard) Key(r *virtualkeyboard.ZwpVirtualKeyboardV1, time, key | |||||
| 128 | } | 132 | } | ||
| 129 | 133 | ||||
| 130 | func (k *virtualKeyboard) Modifiers(r *virtualkeyboard.ZwpVirtualKeyboardV1, depressed, latched, locked, group uint32) { | 134 | func (k *virtualKeyboard) Modifiers(r *virtualkeyboard.ZwpVirtualKeyboardV1, depressed, latched, locked, group uint32) { | ||
| 135 | + | k.mods = ports.ModState{Depressed: depressed, Latched: latched, Locked: locked, Group: group} | |||
| 131 | for _, kb := range k.keyboards(r) { | 136 | for _, kb := range k.keyboards(r) { | ||
| 132 | kb.SendModifiers(k.server.serial, depressed, latched, locked, group) | 137 | kb.SendModifiers(k.server.serial, depressed, latched, locked, group) | ||
| 133 | } | 138 | } | ||
| @@ -164,13 +169,25 @@ func (s *Server) useKeymap(k *virtualKeyboard) { | |||||
| 164 | if s.keymapOwner == k { | 169 | if s.keymapOwner == k { | ||
| 165 | return | 170 | return | ||
| 166 | } | 171 | } | ||
| 172 | + | old := s.activeKeymapText() | |||
| 167 | s.keymapOwner = k | 173 | s.keymapOwner = k | ||
| 168 | - | s.sendKeymapAll(s.currentKeymap()) | 174 | + | // Like wlroots and smithay, a keymap equal to the active one is not |
| 169 | - | if k == nil { | 175 | + | // sent again: tools such as dictation apps create a virtual keyboard |
| 170 | - | s.serial++ | 176 | + | // with the seat layout per use, and each resend makes every client |
| 171 | - | for _, kb := range s.clientKeyboards(s.focusClient()) { | 177 | + | // (Xwayland included) recompile its keymap. |
| 172 | - | s.sendModifiers(kb) | 178 | + | if s.activeKeymapText() != old { |
| 173 | - | } | 179 | + | s.sendKeymapAll(s.currentKeymap()) |
| 180 | + | } | |||
| 181 | + | // Modifiers follow the keyboard that types: a skipped keymap no longer | |||
| 182 | + | // resets them, so a virtual keyboard never types through seat Shift or | |||
| 183 | + | // Caps Lock, and the seat gets its own back. | |||
| 184 | + | m := s.modState | |||
| 185 | + | if k != nil { | |||
| 186 | + | m = k.mods | |||
| 187 | + | } | |||
| 188 | + | s.serial++ | |||
| 189 | + | for _, kb := range s.clientKeyboards(s.focusClient()) { | |||
| 190 | + | kb.SendModifiers(s.serial, m.Depressed, m.Latched, m.Locked, m.Group) | |||
| 174 | } | 191 | } | ||
| 175 | s.log.Debug().Bool("virtual", k != nil).Msg("keymap switched") | 192 | s.log.Debug().Bool("virtual", k != nil).Msg("keymap switched") | ||
| 176 | } | 193 | } | ||
| @@ -185,6 +202,14 @@ func (s *Server) sendKeymapAll(fd int, size uint32) { | |||||
| 185 | } | 202 | } | ||
| 186 | } | 203 | } | ||
| 187 | 204 | ||||
| 205 | + | // activeKeymapText is the text of the keymap keyboards carry. | |||
| 206 | + | func (s *Server) activeKeymapText() string { | |||
| 207 | + | if k := s.keymapOwner; k != nil { | |||
| 208 | + | return k.text | |||
| 209 | + | } | |||
| 210 | + | return s.keymapText | |||
| 211 | + | } | |||
| 212 | + | ||||
| 188 | // currentKeymap is the keymap a new keyboard must start with. | 213 | // currentKeymap is the keymap a new keyboard must start with. | ||
| 189 | func (s *Server) currentKeymap() (int, uint32) { | 214 | func (s *Server) currentKeymap() (int, uint32) { | ||
| 190 | if k := s.keymapOwner; k != nil { | 215 | if k := s.keymapOwner; k != nil { | ||
M internal/adapters/wayland/virtualkeyboard_test.go +80 -2
| @@ -69,7 +69,7 @@ func TestVirtualKeyboardTypesIntoFocus(t *testing.T) { | |||
| 69 | 69 | if err := target.Roundtrip(); err != nil { | |
| 70 | 70 | t.Fatal(err) | |
| 71 | 71 | } | |
| 72 | - | want := []uint16{uint16(wayland.KeyboardEventKeymap), uint16(wayland.KeyboardEventKey), uint16(wayland.KeyboardEventKey)} | |
| 72 | + | want := []uint16{uint16(wayland.KeyboardEventKeymap), uint16(wayland.KeyboardEventModifiers), uint16(wayland.KeyboardEventKey), uint16(wayland.KeyboardEventKey)} | |
| 73 | 73 | if !slices.Equal(proxy.opcodes, want) { | |
| 74 | 74 | t.Fatalf("virtual events %v, want %v", proxy.opcodes, want) | |
| 75 | 75 | } | |
| @@ -142,7 +142,8 @@ func TestVirtualKeyboardDisconnectReleases(t *testing.T) { | |||
| 142 | 142 | } | |
| 143 | 143 | k, m := uint16(wayland.KeyboardEventKeymap), uint16(wayland.KeyboardEventModifiers) | |
| 144 | 144 | key := uint16(wayland.KeyboardEventKey) | |
| 145 | - | want := []uint16{k, m, key, key, k, m} | |
| 145 | + | // The Modifiers request comes after the switch's own modifiers event. | |
| 146 | + | want := []uint16{k, m, m, key, key, k, m} | |
| 146 | 147 | if !slices.Equal(proxy.opcodes, want) { | |
| 147 | 148 | t.Fatalf("events %v, want %v", proxy.opcodes, want) | |
| 148 | 149 | } | |
| @@ -180,3 +181,80 @@ func TestVirtualKeyBeforeKeymap(t *testing.T) { | |||
| 180 | 181 | requestProtocol(t, typer, kb, virtualkeyboard.ZwpVirtualKeyboardV1RequestKey, uint32(1), uint32(30), uint32(1)) | |
| 181 | 182 | expectProtocolError(t, typer, kb, uint32(virtualkeyboard.ZwpVirtualKeyboardV1ErrorNoKeymap)) | |
| 182 | 183 | } | |
| 184 | + | ||
| 185 | + | // A virtual keyboard with the seat keymap (dictation tools, wtype) sends | |
| 186 | + | // no keymap to clients: only its keys, then no keymap on the switch back. | |
| 187 | + | func TestVirtualKeyboardSameKeymapNotResent(t *testing.T) { | |
| 188 | + | s, events, commands, dir := keyboardServer(t) | |
| 189 | + | target, proxy := focusedTarget(t, s, events, commands, dir) | |
| 190 | + | for range 2 { // one typer per use, like a dictation app | |
| 191 | + | typer, kb := virtualTyper(t, s, dir) | |
| 192 | + | sendVirtualKeymap(t, typer, kb, keymapText(t)) | |
| 193 | + | requestProtocol(t, typer, kb, virtualkeyboard.ZwpVirtualKeyboardV1RequestKey, uint32(1), uint32(30), uint32(1)) | |
| 194 | + | requestProtocol(t, typer, kb, virtualkeyboard.ZwpVirtualKeyboardV1RequestKey, uint32(2), uint32(30), uint32(0)) | |
| 195 | + | roundtrip(t, typer, target) | |
| 196 | + | _ = typer.Close() | |
| 197 | + | } | |
| 198 | + | deadline := time.Now().Add(200 * time.Millisecond) | |
| 199 | + | for time.Now().Before(deadline) { | |
| 200 | + | roundtrip(t, target) | |
| 201 | + | time.Sleep(5 * time.Millisecond) | |
| 202 | + | } | |
| 203 | + | if len(proxy.keymaps) != 0 { | |
| 204 | + | t.Fatalf("%d keymaps sent for a virtual keyboard with the seat keymap", len(proxy.keymaps)) | |
| 205 | + | } | |
| 206 | + | keys := 0 | |
| 207 | + | for _, o := range proxy.opcodes { | |
| 208 | + | if o == uint16(wayland.KeyboardEventKey) { | |
| 209 | + | keys++ | |
| 210 | + | } | |
| 211 | + | } | |
| 212 | + | if keys != 4 { | |
| 213 | + | t.Fatalf("keys %d, want 4 (events %v)", keys, proxy.opcodes) | |
| 214 | + | } | |
| 215 | + | } | |
| 216 | + | ||
| 217 | + | // A virtual keyboard with the seat keymap types without the seat's | |
| 218 | + | // modifiers: its switch sends its own (none), and the seat's come back | |
| 219 | + | // with the next real key. | |
| 220 | + | func TestVirtualKeyboardSameKeymapOwnModifiers(t *testing.T) { | |
| 221 | + | s, events, commands, dir := keyboardServer(t) | |
| 222 | + | target, proxy := focusedTarget(t, s, events, commands, dir) | |
| 223 | + | const capsLocked = 2 | |
| 224 | + | set := make(chan struct{}) | |
| 225 | + | s.display.Do(func() { s.modState = ports.ModState{Locked: capsLocked}; close(set) }) | |
| 226 | + | <-set | |
| 227 | + | proxy.locked = nil | |
| 228 | + | typer, kb := virtualTyper(t, s, dir) | |
| 229 | + | sendVirtualKeymap(t, typer, kb, keymapText(t)) | |
| 230 | + | requestProtocol(t, typer, kb, virtualkeyboard.ZwpVirtualKeyboardV1RequestKey, uint32(1), uint32(30), uint32(1)) | |
| 231 | + | roundtrip(t, typer, target) | |
| 232 | + | m, key := uint16(wayland.KeyboardEventModifiers), uint16(wayland.KeyboardEventKey) | |
| 233 | + | if !slices.Equal(proxy.opcodes, []uint16{m, key}) || !slices.Equal(proxy.locked, []uint32{0}) { | |
| 234 | + | t.Fatalf("events %v locked %v, want modifiers with nothing locked, then the key", proxy.opcodes, proxy.locked) | |
| 235 | + | } | |
| 236 | + | } | |
| 237 | + | ||
| 238 | + | // A virtual keymap that differs from the seat's is sent on switch, on | |
| 239 | + | // update while it owns the keyboards, and the seat's again on switch back. | |
| 240 | + | func TestVirtualKeyboardOtherKeymapSent(t *testing.T) { | |
| 241 | + | s, events, commands, dir := keyboardServer(t) | |
| 242 | + | target, proxy := focusedTarget(t, s, events, commands, dir) | |
| 243 | + | typer, kb := virtualTyper(t, s, dir) | |
| 244 | + | first := strings.Replace(keymapText(t), "xkb_keymap", "xkb_keymap ", 1) | |
| 245 | + | second := strings.Replace(keymapText(t), "xkb_keymap", "xkb_keymap ", 1) | |
| 246 | + | sendVirtualKeymap(t, typer, kb, first) | |
| 247 | + | requestProtocol(t, typer, kb, virtualkeyboard.ZwpVirtualKeyboardV1RequestKey, uint32(1), uint32(30), uint32(1)) | |
| 248 | + | sendVirtualKeymap(t, typer, kb, second) | |
| 249 | + | requestProtocol(t, typer, kb, virtualkeyboard.ZwpVirtualKeyboardV1RequestKey, uint32(2), uint32(30), uint32(0)) | |
| 250 | + | roundtrip(t, typer, target) | |
| 251 | + | _ = typer.Close() | |
| 252 | + | deadline := time.Now().Add(2 * time.Second) | |
| 253 | + | for len(proxy.keymaps) < 3 && time.Now().Before(deadline) { | |
| 254 | + | roundtrip(t, target) | |
| 255 | + | time.Sleep(5 * time.Millisecond) | |
| 256 | + | } | |
| 257 | + | if !slices.Equal(proxy.keymaps, []string{first, second, keymapText(t)}) { | |
| 258 | + | t.Fatalf("keymaps %d, want virtual, updated virtual, seat", len(proxy.keymaps)) | |
| 259 | + | } | |
| 260 | + | } | |
| @@ -69,7 +69,7 @@ func TestVirtualKeyboardTypesIntoFocus(t *testing.T) { | |||||
| 69 | if err := target.Roundtrip(); err != nil { | 69 | if err := target.Roundtrip(); err != nil { | ||
| 70 | t.Fatal(err) | 70 | t.Fatal(err) | ||
| 71 | } | 71 | } | ||
| 72 | - | want := []uint16{uint16(wayland.KeyboardEventKeymap), uint16(wayland.KeyboardEventKey), uint16(wayland.KeyboardEventKey)} | 72 | + | want := []uint16{uint16(wayland.KeyboardEventKeymap), uint16(wayland.KeyboardEventModifiers), uint16(wayland.KeyboardEventKey), uint16(wayland.KeyboardEventKey)} |
| 73 | if !slices.Equal(proxy.opcodes, want) { | 73 | if !slices.Equal(proxy.opcodes, want) { | ||
| 74 | t.Fatalf("virtual events %v, want %v", proxy.opcodes, want) | 74 | t.Fatalf("virtual events %v, want %v", proxy.opcodes, want) | ||
| 75 | } | 75 | } | ||
| @@ -142,7 +142,8 @@ func TestVirtualKeyboardDisconnectReleases(t *testing.T) { | |||||
| 142 | } | 142 | } | ||
| 143 | k, m := uint16(wayland.KeyboardEventKeymap), uint16(wayland.KeyboardEventModifiers) | 143 | k, m := uint16(wayland.KeyboardEventKeymap), uint16(wayland.KeyboardEventModifiers) | ||
| 144 | key := uint16(wayland.KeyboardEventKey) | 144 | key := uint16(wayland.KeyboardEventKey) | ||
| 145 | - | want := []uint16{k, m, key, key, k, m} | 145 | + | // The Modifiers request comes after the switch's own modifiers event. |
| 146 | + | want := []uint16{k, m, m, key, key, k, m} | |||
| 146 | if !slices.Equal(proxy.opcodes, want) { | 147 | if !slices.Equal(proxy.opcodes, want) { | ||
| 147 | t.Fatalf("events %v, want %v", proxy.opcodes, want) | 148 | t.Fatalf("events %v, want %v", proxy.opcodes, want) | ||
| 148 | } | 149 | } | ||
| @@ -180,3 +181,80 @@ func TestVirtualKeyBeforeKeymap(t *testing.T) { | |||||
| 180 | requestProtocol(t, typer, kb, virtualkeyboard.ZwpVirtualKeyboardV1RequestKey, uint32(1), uint32(30), uint32(1)) | 181 | requestProtocol(t, typer, kb, virtualkeyboard.ZwpVirtualKeyboardV1RequestKey, uint32(1), uint32(30), uint32(1)) | ||
| 181 | expectProtocolError(t, typer, kb, uint32(virtualkeyboard.ZwpVirtualKeyboardV1ErrorNoKeymap)) | 182 | expectProtocolError(t, typer, kb, uint32(virtualkeyboard.ZwpVirtualKeyboardV1ErrorNoKeymap)) | ||
| 182 | } | 183 | } | ||
| 184 | + | ||||
| 185 | + | // A virtual keyboard with the seat keymap (dictation tools, wtype) sends | |||
| 186 | + | // no keymap to clients: only its keys, then no keymap on the switch back. | |||
| 187 | + | func TestVirtualKeyboardSameKeymapNotResent(t *testing.T) { | |||
| 188 | + | s, events, commands, dir := keyboardServer(t) | |||
| 189 | + | target, proxy := focusedTarget(t, s, events, commands, dir) | |||
| 190 | + | for range 2 { // one typer per use, like a dictation app | |||
| 191 | + | typer, kb := virtualTyper(t, s, dir) | |||
| 192 | + | sendVirtualKeymap(t, typer, kb, keymapText(t)) | |||
| 193 | + | requestProtocol(t, typer, kb, virtualkeyboard.ZwpVirtualKeyboardV1RequestKey, uint32(1), uint32(30), uint32(1)) | |||
| 194 | + | requestProtocol(t, typer, kb, virtualkeyboard.ZwpVirtualKeyboardV1RequestKey, uint32(2), uint32(30), uint32(0)) | |||
| 195 | + | roundtrip(t, typer, target) | |||
| 196 | + | _ = typer.Close() | |||
| 197 | + | } | |||
| 198 | + | deadline := time.Now().Add(200 * time.Millisecond) | |||
| 199 | + | for time.Now().Before(deadline) { | |||
| 200 | + | roundtrip(t, target) | |||
| 201 | + | time.Sleep(5 * time.Millisecond) | |||
| 202 | + | } | |||
| 203 | + | if len(proxy.keymaps) != 0 { | |||
| 204 | + | t.Fatalf("%d keymaps sent for a virtual keyboard with the seat keymap", len(proxy.keymaps)) | |||
| 205 | + | } | |||
| 206 | + | keys := 0 | |||
| 207 | + | for _, o := range proxy.opcodes { | |||
| 208 | + | if o == uint16(wayland.KeyboardEventKey) { | |||
| 209 | + | keys++ | |||
| 210 | + | } | |||
| 211 | + | } | |||
| 212 | + | if keys != 4 { | |||
| 213 | + | t.Fatalf("keys %d, want 4 (events %v)", keys, proxy.opcodes) | |||
| 214 | + | } | |||
| 215 | + | } | |||
| 216 | + | ||||
| 217 | + | // A virtual keyboard with the seat keymap types without the seat's | |||
| 218 | + | // modifiers: its switch sends its own (none), and the seat's come back | |||
| 219 | + | // with the next real key. | |||
| 220 | + | func TestVirtualKeyboardSameKeymapOwnModifiers(t *testing.T) { | |||
| 221 | + | s, events, commands, dir := keyboardServer(t) | |||
| 222 | + | target, proxy := focusedTarget(t, s, events, commands, dir) | |||
| 223 | + | const capsLocked = 2 | |||
| 224 | + | set := make(chan struct{}) | |||
| 225 | + | s.display.Do(func() { s.modState = ports.ModState{Locked: capsLocked}; close(set) }) | |||
| 226 | + | <-set | |||
| 227 | + | proxy.locked = nil | |||
| 228 | + | typer, kb := virtualTyper(t, s, dir) | |||
| 229 | + | sendVirtualKeymap(t, typer, kb, keymapText(t)) | |||
| 230 | + | requestProtocol(t, typer, kb, virtualkeyboard.ZwpVirtualKeyboardV1RequestKey, uint32(1), uint32(30), uint32(1)) | |||
| 231 | + | roundtrip(t, typer, target) | |||
| 232 | + | m, key := uint16(wayland.KeyboardEventModifiers), uint16(wayland.KeyboardEventKey) | |||
| 233 | + | if !slices.Equal(proxy.opcodes, []uint16{m, key}) || !slices.Equal(proxy.locked, []uint32{0}) { | |||
| 234 | + | t.Fatalf("events %v locked %v, want modifiers with nothing locked, then the key", proxy.opcodes, proxy.locked) | |||
| 235 | + | } | |||
| 236 | + | } | |||
| 237 | + | ||||
| 238 | + | // A virtual keymap that differs from the seat's is sent on switch, on | |||
| 239 | + | // update while it owns the keyboards, and the seat's again on switch back. | |||
| 240 | + | func TestVirtualKeyboardOtherKeymapSent(t *testing.T) { | |||
| 241 | + | s, events, commands, dir := keyboardServer(t) | |||
| 242 | + | target, proxy := focusedTarget(t, s, events, commands, dir) | |||
| 243 | + | typer, kb := virtualTyper(t, s, dir) | |||
| 244 | + | first := strings.Replace(keymapText(t), "xkb_keymap", "xkb_keymap ", 1) | |||
| 245 | + | second := strings.Replace(keymapText(t), "xkb_keymap", "xkb_keymap ", 1) | |||
| 246 | + | sendVirtualKeymap(t, typer, kb, first) | |||
| 247 | + | requestProtocol(t, typer, kb, virtualkeyboard.ZwpVirtualKeyboardV1RequestKey, uint32(1), uint32(30), uint32(1)) | |||
| 248 | + | sendVirtualKeymap(t, typer, kb, second) | |||
| 249 | + | requestProtocol(t, typer, kb, virtualkeyboard.ZwpVirtualKeyboardV1RequestKey, uint32(2), uint32(30), uint32(0)) | |||
| 250 | + | roundtrip(t, typer, target) | |||
| 251 | + | _ = typer.Close() | |||
| 252 | + | deadline := time.Now().Add(2 * time.Second) | |||
| 253 | + | for len(proxy.keymaps) < 3 && time.Now().Before(deadline) { | |||
| 254 | + | roundtrip(t, target) | |||
| 255 | + | time.Sleep(5 * time.Millisecond) | |||
| 256 | + | } | |||
| 257 | + | if !slices.Equal(proxy.keymaps, []string{first, second, keymapText(t)}) { | |||
| 258 | + | t.Fatalf("keymaps %d, want virtual, updated virtual, seat", len(proxy.keymaps)) | |||
| 259 | + | } | |||
| 260 | + | } | |||