1package wayland
2
3import (
4 "context"
5 "encoding/binary"
6 "fmt"
7 "github.com/bnema/purego-libwayland/protocol/relativepointer"
8 "os"
9 "slices"
10 "sync"
11 "time"
12
13 "github.com/bnema/neferwl/internal/ports"
14 "github.com/bnema/purego-libwayland/protocol/fractionalscale"
15 "github.com/bnema/purego-libwayland/protocol/wayland"
16 "github.com/bnema/purego-libwayland/server"
17 "github.com/bnema/zerowrap"
18 "golang.org/x/sys/unix"
19)
20
21// Options configures the server. Outputs is the initial layout; core
22// replaces it with ports.SetOutputs.
23type Options struct {
24 RuntimeDir string
25 Outputs ports.Layout
26 // DMABuf is what the renderer imports; empty disables linux-dmabuf.
27 DMABuf ports.DMABufSupport
28 // SyncobjNode is the render node (/dev/dri/renderD*) timeline
29 // syncobjs are imported on; empty or without timeline support, no
30 // explicit sync is offered.
31 SyncobjNode string
32 Keymap string
33 RepeatRate, RepeatDelay int
34 // syncDev replaces the render node's syncobj interface (tests).
35 syncDev syncobjDevice
36}
37
38// Channels carries client notifications and commands. Events may be unbuffered.
39type Channels struct {
40 Events chan<- ports.ClientEvent
41 Contents chan<- ports.SurfaceContent
42 Commands <-chan ports.ClientCommand
43 // Cursors receives the cursor the client under the pointer asks for.
44 Cursors chan<- ports.CursorChange
45 // Presented paces frame callbacks on the outputs' page flips; outputs
46 // that do not flip (idle, headless) are paced at their refresh rate.
47 Presented <-chan ports.OutputPresented
48 // OutputFormats are the outputs' direct scanout formats, offered in
49 // dmabuf feedback to fullscreen surfaces.
50 OutputFormats <-chan ports.OutputFormats
51}
52type Server struct {
53 display *server.Display
54 env procEnv
55 slotsPending bool // core waits for a slot window
56 name string
57 cleanup func()
58 log zerowrap.Logger
59 channels Channels
60 // awaiting holds frame callbacks by output name, due at its next
61 // frame (frameDue, or its page flip).
62 awaiting map[string][]*wayland.Callback
63 frameDue map[string]time.Time
64 frameReady chan struct{}
65 // held buffers wait until no output reads them (release.go); reports
66 // are the outputs' latest ports.OutputPresented.
67 held []heldBuffer
68 reports map[string]ports.OutputPresented
69 frames uint64
70 started time.Time
71 // fifoSurfaces have a fifo barrier or queued commits (fifo.go);
72 // lastFlip is each output's latest page flip.
73 fifoSurfaces map[*surface]struct{}
74 lastFlip map[string]time.Time
75 // tokens are the issued xdg-activation tokens (activation.go).
76 tokens map[string]activationToken
77 surfaces map[*server.Resource]*surface
78 buffers map[*server.Resource]clientBuffer
79 dmabuf *dmabufGlobal
80 serial uint32
81 // press is the serial of the last button or key press, sent to
82 // pressClient: popup grabs must come from it.
83 press uint32
84 pressClient server.Client
85 // pressAt and focusAt date the last press and keyboard focus change,
86 // for xdg-activation tokens.
87 pressAt, focusAt time.Time
88 ctx context.Context
89 windows map[ports.WindowID]*window
90 layers map[ports.WindowID]*layerSurface
91 nextWindow ports.WindowID
92 nextPool uint64
93 focused ports.WindowID
94 pointerFocus ports.WindowID
95 wheelRest [2]int32 // v120 not yet sent as axis_discrete, per axis
96 wheelHeld [2]float64 // axis value held back with it for pre-v8 clients
97 pointerX, pointerY float64
98 pointers map[server.Client][]*wayland.Pointer
99 modState ports.ModState
100 keyboards map[server.Client][]*wayland.Keyboard
101 heldKeys map[uint32]bool
102 keymapFD int
103 keymapSize uint32
104 keymapText string // the seat keymap, to compare virtual keymaps with
105 keymapOwner *virtualKeyboard // nil: keyboards carry the seat keymap
106 repeatRate, repeatDelay int
107 // eventMu protects only the notification queue, not display-owned window state.
108 // The event queue is unbounded by design: it grows only if core stops draining
109 // Events, which happens only at shutdown (core owns the receiving end). A
110 // stalled core is a bug surfaced by ctx cancellation, not a reason to block
111 // the display goroutine.
112 eventMu sync.Mutex
113 events []ports.ClientEvent
114 eventReady chan struct{}
115 contentMu sync.Mutex
116 contents map[ports.WindowID]ports.SurfaceContent
117 contentSeq map[ports.WindowID]uint64
118 // damage is each window's recent damage (contentMu).
119 damage map[ports.WindowID][]ports.SeqDamage
120 // feedbacks wait for the flip that shows their content (presentation.go).
121 feedbacks []feedbackWait
122 // Explicit sync (syncobj.go): the render node, imported timelines by
123 // resource, and the acquire point waiter.
124 syncDev syncobjDevice
125 timelines map[*server.Resource]*timeline
126 syncWait *syncWaiter
127 // flips is each output's latest flip (pacer, under Do).
128 flips map[string]ports.FlipInfo
129 // inhibitors are the live inhibitor objects (inhibit.go);
130 // shortcutWindows and idleWindows what core was told.
131 inhibitors []*inhibitor
132 shortcutWindows, idleWindows map[ports.WindowID]bool
133 contentNotify chan struct{}
134 contentReady chan struct{}
135 outputs []*output
136 focusedOutput string
137 fractions map[*surface]*fractionalscale.WpFractionalScaleV1
138 // cursorSurface is the wl_pointer.set_cursor surface in use.
139 cursorSurface *surface
140 // cursorMu guards only the latest cursor change for forwardCursors.
141 cursorMu sync.Mutex
142 cursorLatest ports.CursorChange
143 cursorQueued bool
144 cursorReady chan struct{}
145 // Clipboard and primary selection (clipboard.go).
146 selections [2]*clipSource
147 clipDevices []*clipDevice
148 dataSources, primarySources, controlSources map[*server.Resource]*clipSource
149 // Pointer constraints (constraints.go).
150 regions map[*server.Resource]*region
151 relatives map[server.Client][]*relativepointer.ZwpRelativePointerV1
152 constraints map[*surface]*constraint
153 constraint *constraint // the active one
154 positioners map[*server.Resource]*positioner
155}
156
157func removeItem[T comparable](list []T, v T) []T {
158 for i, x := range list {
159 if x == v {
160 return append(list[:i], list[i+1:]...)
161 }
162 }
163 return list
164}
165
166func New(opts Options, ch Channels, log zerowrap.Logger) (*Server, error) {
167 if opts.RuntimeDir == "" {
168 opts.RuntimeDir = os.Getenv("XDG_RUNTIME_DIR")
169 }
170 if opts.RuntimeDir == "" {
171 return nil, fmt.Errorf("XDG_RUNTIME_DIR is empty")
172 }
173 d, err := server.NewDisplay()
174 if err != nil {
175 return nil, err
176 }
177 name, fd, cleanup, err := listen(opts.RuntimeDir)
178 if err != nil {
179 d.Close()
180 return nil, err
181 }
182 if err = d.AddSocketFD(fd); err != nil {
183 unix.Close(fd)
184 cleanup()
185 d.Close()
186 return nil, err
187 }
188 s := &Server{display: d, awaiting: map[string][]*wayland.Callback{}, frameDue: map[string]time.Time{}, frameReady: make(chan struct{}, 1), reports: map[string]ports.OutputPresented{}, env: linuxProcEnv{}, name: name, cleanup: cleanup, log: log, channels: ch, surfaces: make(map[*server.Resource]*surface), buffers: make(map[*server.Resource]clientBuffer), windows: make(map[ports.WindowID]*window), layers: make(map[ports.WindowID]*layerSurface), nextWindow: 1, eventReady: make(chan struct{}, 1), contents: make(map[ports.WindowID]ports.SurfaceContent), contentSeq: make(map[ports.WindowID]uint64), damage: map[ports.WindowID][]ports.SeqDamage{}, contentReady: make(chan struct{}, 1), cursorReady: make(chan struct{}, 1), dataSources: map[*server.Resource]*clipSource{}, primarySources: map[*server.Resource]*clipSource{}, controlSources: map[*server.Resource]*clipSource{}, contentNotify: make(chan struct{}), keymapFD: -1, keyboards: make(map[server.Client][]*wayland.Keyboard), pointers: make(map[server.Client][]*wayland.Pointer), regions: map[*server.Resource]*region{}, relatives: map[server.Client][]*relativepointer.ZwpRelativePointerV1{}, constraints: map[*surface]*constraint{}, positioners: map[*server.Resource]*positioner{}, repeatRate: opts.RepeatRate, repeatDelay: opts.RepeatDelay}
189 s.fractions = map[*surface]*fractionalscale.WpFractionalScaleV1{}
190 s.fifoSurfaces, s.lastFlip = map[*surface]struct{}{}, map[string]time.Time{}
191 s.flips = map[string]ports.FlipInfo{}
192 s.timelines = map[*server.Resource]*timeline{}
193 var node *renderNode
194 if opts.syncDev != nil {
195 s.syncDev = opts.syncDev
196 if s.syncWait, err = newSyncWaiter(s.wakePacer); err != nil {
197 cleanup()
198 d.Close()
199 return nil, err
200 }
201 } else if opts.SyncobjNode != "" {
202 if node, err = openSyncobj(opts.SyncobjNode); err != nil {
203 log.Info().Str("component", "wayland").Err(err).Msg("explicit sync off")
204 node = nil
205 } else if s.syncWait, err = newSyncWaiter(s.wakePacer); err != nil {
206 node.close()
207 node = nil
208 } else {
209 s.syncDev = node
210 }
211 }
212 s.tokens = map[string]activationToken{}
213 if opts.Keymap != "" {
214 fd, size, e := keymapFile(opts.Keymap)
215 if e != nil {
216 cleanup()
217 d.Close()
218 return nil, e
219 }
220 s.keymapFD, s.keymapSize, s.keymapText = fd, size, opts.Keymap
221 }
222 s.cleanup = func() {
223 if s.keymapFD >= 0 {
224 unix.Close(s.keymapFD)
225 }
226 s.dmabuf.close()
227 if s.syncWait != nil {
228 s.syncWait.close()
229 }
230 if node != nil {
231 node.close()
232 }
233 cleanup()
234 }
235 if err = registerGlobals(d, opts, s); err != nil {
236 s.cleanup()
237 d.Close()
238 return nil, err
239 }
240 for _, p := range opts.Outputs {
241 s.addOutput(p)
242 }
243 return s, nil
244}
245func (s *Server) SocketName() string { return s.name }
246func (s *Server) Run(ctx context.Context) error {
247 defer s.cleanup()
248 s.log.Info().Str("socket", s.name).Msg("starting wayland")
249 s.started = time.Now()
250 s.ctx = ctx
251 var wg sync.WaitGroup
252 wg.Add(6)
253 go func() { defer wg.Done(); s.forward(ctx) }()
254 go func() { defer wg.Done(); s.forwardCursors(ctx) }()
255 go func() { defer wg.Done(); s.forwardContents(ctx) }()
256 go func() {
257 defer wg.Done()
258 for {
259 select {
260 case <-ctx.Done():
261 return
262 case <-s.display.Stopped():
263 return
264 case cmd, ok := <-s.channels.Commands:
265 if !ok {
266 return
267 }
268 if ctx.Err() != nil {
269 return
270 }
271 // One display round trip per batch: Do waits for the event
272 // loop, so a round trip per motion capped pointer events at
273 // ~150/s and let a backlog of stale motions build up.
274 batch, open := drainCommands(cmd, s.channels.Commands)
275 if !s.display.Do(func() {
276 for _, c := range batch {
277 if ctx.Err() != nil {
278 return
279 }
280 s.apply(c)
281 }
282 }) || !open {
283 return
284 }
285 }
286 }
287 }()
288 go func() { defer wg.Done(); s.pace(ctx) }()
289 go func() { defer wg.Done(); s.forwardOutputFormats(ctx) }()
290 if s.syncWait != nil {
291 wg.Add(1)
292 go func() { defer wg.Done(); s.syncWait.run(ctx) }()
293 }
294 err := s.display.Run(ctx)
295 wg.Wait()
296 return err
297}
298
299// damageHistory is how many contents a window's damage history covers:
300// enough for a renderer target a few frames old.
301const damageHistory = 4
302
303// forwardOutputFormats applies the outputs' scanout formats on the
304// display goroutine.
305func (s *Server) forwardOutputFormats(ctx context.Context) {
306 for {
307 select {
308 case <-ctx.Done():
309 return
310 case <-s.display.Stopped():
311 return
312 case f, ok := <-s.channels.OutputFormats:
313 if !ok {
314 return
315 }
316 if !s.display.Do(func() {
317 if s.dmabuf != nil {
318 s.dmabuf.setOutputFormats(f)
319 }
320 }) {
321 return
322 }
323 }
324 }
325}
326
327func (s *Server) emit(ev ports.ClientEvent) {
328 if s.channels.Events == nil {
329 return
330 }
331 s.eventMu.Lock()
332 s.events = append(s.events, ev)
333 s.eventMu.Unlock()
334 select {
335 case s.eventReady <- struct{}{}:
336 default:
337 }
338}
339
340func (s *Server) forward(ctx context.Context) {
341 for {
342 s.eventMu.Lock()
343 if len(s.events) == 0 {
344 s.eventMu.Unlock()
345 select {
346 case <-ctx.Done():
347 return
348 case <-s.eventReady:
349 continue
350 }
351 }
352 ev := s.events[0]
353 s.events[0] = nil
354 s.events = s.events[1:]
355 s.eventMu.Unlock()
356 select {
357 case <-ctx.Done():
358 return
359 case s.channels.Events <- ev:
360 }
361 }
362}
363
364func (s *Server) emitContent(c ports.SurfaceContent, d damage) {
365 if s.channels.Contents == nil {
366 return
367 }
368 s.contentMu.Lock()
369 s.contentSeq[c.ID]++
370 c.Seq = s.contentSeq[c.ID]
371 // The window's damage history: its last damageHistory contents.
372 h := append(s.damage[c.ID], ports.SeqDamage{Seq: c.Seq, Full: d.full, Rects: d.rects})
373 if len(h) > damageHistory {
374 h = h[len(h)-damageHistory:]
375 }
376 if c.Empty() {
377 delete(s.damage, c.ID)
378 } else {
379 s.damage[c.ID] = h
380 }
381 c.DamageHistory = slices.Clone(h)
382 s.contents[c.ID] = c
383 close(s.contentNotify)
384 s.contentNotify = make(chan struct{})
385 s.contentMu.Unlock()
386 select {
387 case s.contentReady <- struct{}{}:
388 default:
389 }
390}
391
392func (s *Server) forwardContents(ctx context.Context) {
393 for {
394 s.contentMu.Lock()
395 var id ports.WindowID
396 var c ports.SurfaceContent
397 found := false
398 var seq uint64
399 var notify <-chan struct{}
400 for id, c = range s.contents {
401 found = true
402 seq = s.contentSeq[id]
403 break
404 }
405 notify = s.contentNotify
406 s.contentMu.Unlock()
407 if !found {
408 select {
409 case <-ctx.Done():
410 return
411 case <-s.contentReady:
412 continue
413 }
414 }
415 // Do not hold a stale frame while the receiver is busy: wake on newer data.
416 select {
417 case <-ctx.Done():
418 return
419 case <-notify:
420 continue
421 case s.channels.Contents <- c:
422 s.contentMu.Lock()
423 if s.contentSeq[id] == seq {
424 delete(s.contents, id)
425 }
426 s.contentMu.Unlock()
427 }
428 }
429}
430
431// maxCommandBatch bounds how many queued commands one display round trip applies.
432const maxCommandBatch = 256
433
434// drainCommands returns first plus the commands already queued, with runs of
435// pointer motion for the same window collapsed into the latest one, their
436// relative deltas summed. open is
437// false once the channel is closed.
438func drainCommands(first ports.ClientCommand, cmds <-chan ports.ClientCommand) (batch []ports.ClientCommand, open bool) {
439 batch = append(batch, first)
440 for len(batch) < maxCommandBatch {
441 select {
442 case c, ok := <-cmds:
443 if !ok {
444 return batch, false
445 }
446 if m, isMotion := c.(ports.PointerMotionTo); isMotion {
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
454 batch[len(batch)-1] = m
455 continue
456 }
457 }
458 batch = append(batch, c)
459 default:
460 return batch, true
461 }
462 }
463 return batch, true
464}
465
466func (s *Server) apply(cmd ports.ClientCommand) {
467 switch c := cmd.(type) {
468 case ports.PointerFocus:
469 s.changePointerFocus(c.ID, c.X, c.Y)
470 case ports.PointerMotionTo:
471 if l := s.layers[c.ID]; l != nil && c.ID == s.pointerFocus {
472 s.pointerX, s.pointerY = c.X, c.Y
473 for _, p := range s.layerPointers(l) {
474 p.SendMotion(c.TimeMsec, server.FixedFromFloat(c.X), server.FixedFromFloat(c.Y))
475 pointerFrame(p)
476 }
477 return
478 }
479 if w := s.windows[c.ID]; w != nil && c.ID == s.pointerFocus {
480 if s.relativeMotion(w, c) && s.locked(w) {
481 for _, p := range s.windowPointers(w) {
482 pointerFrame(p)
483 }
484 }
485 if s.locked(w) {
486 // Only relative motion reaches a locked pointer.
487 return
488 }
489 x, y := w.surfacePoint(c.X, c.Y)
490 s.pointerX, s.pointerY = c.X, c.Y
491 // A constraint waits for the pointer to enter its region.
492 if s.constraint == nil {
493 s.updateConstraint()
494 }
495 for _, p := range s.windowPointers(w) {
496 p.SendMotion(c.TimeMsec, server.FixedFromFloat(x), server.FixedFromFloat(y))
497 pointerFrame(p)
498 }
499 }
500 case ports.PointerButtonTo:
501 // Releases may target the implicit-grab window after focus has moved.
502 if client, pointers, ok := s.pointerTarget(c.ID); ok {
503 s.serial++
504 state := uint32(0)
505 if c.Pressed {
506 state = 1
507 s.press, s.pressClient, s.pressAt = s.serial, client, time.Now()
508 }
509 for _, p := range pointers {
510 p.SendButton(s.serial, c.TimeMsec, c.Button, state)
511 pointerFrame(p)
512 }
513 }
514 case ports.PointerAxisTo:
515 // Scroll applies to the entered surface only.
516 if c.ID != s.pointerFocus {
517 return
518 }
519 steps, values := s.wheelSteps(c.Axis)
520 _, pointers, _ := s.pointerTarget(c.ID)
521 for _, p := range pointers {
522 sendAxis(p, c.Axis, steps, values)
523 }
524 case ports.SetKeymap:
525 s.setKeymap(c)
526 case ports.FocusWindow:
527 if s.focused != c.ID {
528 s.changeFocus(c.ID)
529 }
530 case ports.ForwardKey:
531 _, keyboards := s.focusTarget(c.ID)
532 if c.ID != s.focused || len(keyboards) == 0 {
533 s.log.Debug().Uint64("id", uint64(c.ID)).Msg("ignored forward key")
534 return
535 }
536 s.useKeymap(nil)
537 if s.heldKeys == nil {
538 s.heldKeys = make(map[uint32]bool)
539 }
540 if c.Key.Pressed {
541 s.heldKeys[c.Key.Keycode] = true
542 } else {
543 delete(s.heldKeys, c.Key.Keycode)
544 }
545 s.serial++
546 state := uint32(0)
547 if c.Key.Pressed {
548 state = 1
549 }
550 if c.Key.Pressed {
551 if surf, _ := s.focusTarget(c.ID); surf != nil {
552 s.press, s.pressClient, s.pressAt = s.serial, surf.Client(), time.Now()
553 }
554 }
555 for _, k := range keyboards {
556 k.SendKey(s.serial, c.Key.TimeMsec, c.Key.Keycode, state)
557 }
558 if c.Key.State != s.modState {
559 s.modState = c.Key.State
560 s.serial++
561 for _, k := range keyboards {
562 s.sendModifiers(k)
563 }
564 }
565 case ports.ConfigurePopup:
566 if w := s.windows[c.ID]; w != nil && w.popup != nil {
567 w.popup.configure(c)
568 }
569 case ports.ClosePopup:
570 if w := s.windows[c.ID]; w != nil && w.popup != nil {
571 w.popup.dismiss()
572 }
573 case ports.ConfigureWindow:
574 w := s.windows[c.ID]
575 if w == nil || w.toplevel == nil || !w.toplevel.Resource.Alive() || !w.xdg.resource.Resource.Alive() {
576 s.log.Debug().Uint64("id", uint64(c.ID)).Msg("configure missing window")
577 return
578 }
579 s.log.Info().Uint64("id", uint64(c.ID)).Int("w", c.Width).Int("h", c.Height).Bool("fullscreen", c.Fullscreen).Bool("activated", c.Activated).Msg("configure")
580 scanoutChanged := !w.hasLast || w.last.Fullscreen != c.Fullscreen || w.last.Output != c.Output
581 w.last, w.hasLast = c, true
582 w.sendConfigure()
583 if scanoutChanged && w.xdg.surface != nil {
584 s.dmabuf.resendSurface(w.xdg.surface)
585 }
586 if surf := w.xdg.surface; surf != nil && c.Output != "" {
587 surf.sendTreeScale()
588 }
589 case ports.SetOutputs:
590 s.setOutputs(c)
591 case ports.SlotsPending:
592 s.slotsPending = c.Pending
593 case ports.ShortcutsInhibitState:
594 s.setShortcutsInhibit(c)
595 case ports.CloseWindow:
596 w := s.windows[c.ID]
597 if w == nil || w.toplevel == nil || !w.toplevel.Resource.Alive() {
598 s.log.Debug().Uint64("id", uint64(c.ID)).Msg("close missing window")
599 return
600 }
601 w.toplevel.SendClose()
602 }
603}
604
605func (s *Server) windowKeyboards(w *window) []*wayland.Keyboard {
606 if w == nil || !w.mapped || !w.xdg.resource.Resource.Alive() {
607 return nil
608 }
609 return s.clientKeyboards(w.xdg.resource.Client())
610}
611
612func (s *Server) clientKeyboards(c server.Client) []*wayland.Keyboard {
613 var alive []*wayland.Keyboard
614 for _, k := range s.keyboards[c] {
615 if k.Resource.Alive() {
616 alive = append(alive, k)
617 }
618 }
619 return alive
620}
621
622// focusTarget resolves a window or layer ID to its wl_surface and keyboards.
623func (s *Server) focusTarget(id ports.WindowID) (*wayland.Surface, []*wayland.Keyboard) {
624 if w := s.windows[id]; w != nil && w.mapped {
625 if surf := w.xdg.surfaceResource(); surf != nil {
626 return surf, s.windowKeyboards(w)
627 }
628 }
629 if l := s.layers[id]; l != nil && l.mapped && l.resource.Resource.Alive() {
630 if surf := l.surfaceResource(); surf != nil {
631 return surf, s.clientKeyboards(l.resource.Client())
632 }
633 }
634 return nil, nil
635}
636
637// pointerTarget resolves a mapped window or layer ID to its client and
638// live pointers.
639func (s *Server) pointerTarget(id ports.WindowID) (server.Client, []*wayland.Pointer, bool) {
640 if w := s.windows[id]; w != nil && w.mapped && w.xdg.resource.Resource.Alive() {
641 return w.xdg.resource.Client(), s.windowPointers(w), true
642 }
643 if l := s.layers[id]; l != nil && l.mapped && l.resource.Resource.Alive() {
644 return l.resource.Client(), s.layerPointers(l), true
645 }
646 return server.Client{}, nil, false
647}
648
649// pointerSurface is the wl_surface of a mapped window or layer and the
650// point in its surface coordinates.
651func (s *Server) pointerSurface(id ports.WindowID, x, y float64) (*wayland.Surface, []*wayland.Pointer, float64, float64) {
652 if w := s.windows[id]; w != nil {
653 if surf := w.xdg.surfaceResource(); surf != nil && surf.Resource.Alive() {
654 x, y := w.surfacePoint(x, y)
655 return surf, s.windowPointers(w), x, y
656 }
657 }
658 if l := s.layers[id]; l != nil && l.mapped {
659 if surf := l.surfaceResource(); surf != nil {
660 return surf, s.layerPointers(l), x, y
661 }
662 }
663 return nil, nil, 0, 0
664}
665
666// keymapFile writes a sealed memfd holding the NUL-terminated keymap.
667func keymapFile(keymap string) (int, uint32, error) {
668 if uint64(len(keymap))+1 > uint64(^uint32(0)) {
669 return -1, 0, fmt.Errorf("keymap too large")
670 }
671 fd, err := unix.MemfdCreate("neferwl-keymap", unix.MFD_CLOEXEC|unix.MFD_ALLOW_SEALING)
672 if err != nil {
673 return -1, 0, err
674 }
675 n, err := unix.Write(fd, append([]byte(keymap), 0))
676 if err == nil && n != len(keymap)+1 {
677 err = fmt.Errorf("short keymap write")
678 }
679 if err == nil {
680 _, err = unix.FcntlInt(uintptr(fd), unix.F_ADD_SEALS, unix.F_SEAL_SHRINK|unix.F_SEAL_GROW|unix.F_SEAL_WRITE|unix.F_SEAL_SEAL)
681 }
682 if err != nil {
683 unix.Close(fd)
684 return -1, 0, err
685 }
686 return fd, uint32(n), nil
687}
688
689// setKeymap sends repeat info, and a new keymap when one is given, to every keyboard.
690// A new keymap resets held keys and modifiers: the focused client gets leave, then
691// enter with no keys.
692func (s *Server) setKeymap(c ports.SetKeymap) {
693 s.repeatRate, s.repeatDelay = c.RepeatRate, c.RepeatDelay
694 if c.Keymap == "" {
695 for _, list := range s.keyboards {
696 for _, k := range list {
697 if k.Version() >= 4 {
698 k.SendRepeatInfo(int32(s.repeatRate), int32(s.repeatDelay))
699 }
700 }
701 }
702 s.log.Info().Int("rate", s.repeatRate).Int("delay", s.repeatDelay).Msg("repeat updated")
703 return
704 }
705 fd, size, err := keymapFile(c.Keymap)
706 if err != nil {
707 s.log.Warn().Err(err).Msg("keymap update failed")
708 return
709 }
710 if s.keymapFD >= 0 {
711 unix.Close(s.keymapFD)
712 }
713 s.keymapFD, s.keymapSize, s.keymapText = fd, size, c.Keymap
714 s.keymapOwner = nil
715 focused := s.focused
716 s.changeFocus(0)
717 s.heldKeys = nil
718 s.modState = ports.ModState{}
719 for _, list := range s.keyboards {
720 for _, k := range list {
721 k.SendKeymap(uint32(wayland.KeyboardKeymapFormatXkbV1), s.keymapFD, s.keymapSize)
722 if k.Version() >= 4 {
723 k.SendRepeatInfo(int32(s.repeatRate), int32(s.repeatDelay))
724 }
725 }
726 }
727 s.changeFocus(focused)
728 s.log.Info().Uint32("size", size).Int("rate", s.repeatRate).Int("delay", s.repeatDelay).Msg("keymap updated")
729}
730
731func (s *Server) sendModifiers(k *wayland.Keyboard) {
732 m := s.modState
733 k.SendModifiers(s.serial, m.Depressed, m.Latched, m.Locked, m.Group)
734}
735
736func (s *Server) changeFocus(id ports.WindowID) {
737 // Enter carries seat held keys and modifiers: they need the seat keymap.
738 s.useKeymap(nil)
739 old := s.focusClient()
740 defer func() {
741 // Tokens of a client losing the focus die with it.
742 if now := s.focusClient(); now != old {
743 s.dropTokens(old)
744 s.focusAt = time.Now()
745 }
746 }()
747 if surf, keyboards := s.focusTarget(s.focused); surf != nil {
748 for _, k := range keyboards {
749 s.serial++
750 k.SendLeave(s.serial, surf)
751 }
752 }
753 s.focused = 0
754 if surf, keyboards := s.focusTarget(id); surf != nil {
755 s.focused = id
756 s.focusSelections()
757 for _, k := range keyboards {
758 s.serial++
759 var keys []byte
760 for code := range s.heldKeys {
761 keys = binary.LittleEndian.AppendUint32(keys, code)
762 }
763 k.SendEnter(s.serial, surf, keys)
764 s.sendModifiers(k)
765 }
766 }
767 s.log.Debug().Uint64("id", uint64(s.focused)).Msg("keyboard focus")
768 s.updateConstraint()
769}
770
771// wheelSteps adds wheel v120 to the rest per axis and returns the whole
772// detents for axis_discrete (seat before v8). High-resolution wheels send
773// fractions of 120; a direction change drops the rest, as wlroots does.
774// wheelHeld keeps the axis value of the frames without a step; values is
775// what a pre-v8 client gets with a step: the held value plus this frame's.
776func (s *Server) wheelSteps(a ports.PointerAxis) (steps [2]int32, values [2]float64) {
777 if a.Source != ports.AxisWheel {
778 return steps, values
779 }
780 for i, ax := range [2]ports.ScrollAxis{a.Vertical, a.Horizontal} {
781 if !ax.Set || ax.V120 == 0 {
782 continue
783 }
784 if (s.wheelRest[i] < 0) != (ax.V120 < 0) {
785 s.wheelRest[i], s.wheelHeld[i] = 0, 0
786 }
787 s.wheelRest[i] += ax.V120
788 s.wheelHeld[i] += ax.Value
789 steps[i] = s.wheelRest[i] / 120
790 s.wheelRest[i] -= steps[i] * 120
791 if steps[i] != 0 {
792 values[i], s.wheelHeld[i] = s.wheelHeld[i], 0
793 }
794 }
795 return steps, values
796}
797
798// sendAxis sends one scroll frame, each event gated by the pointer version.
799// steps are whole wheel detents for axis_discrete. A pre-v8 client gets a
800// wheel axis only with a step, carrying values (all the value since the
801// last step), so it never counts smooth and discrete scroll.
802func sendAxis(p *wayland.Pointer, a ports.PointerAxis, steps [2]int32, values [2]float64) {
803 v := p.Version()
804 axes := [2]ports.ScrollAxis{a.Vertical, a.Horizontal}
805 for i := range axes {
806 if v < 8 && a.Source == ports.AxisWheel && axes[i].V120 != 0 && steps[i] == 0 {
807 axes[i].Set = false
808 }
809 }
810 if !axes[0].Set && !axes[1].Set {
811 return
812 }
813 if v >= 5 {
814 p.SendAxisSource(uint32(a.Source))
815 }
816 for axis, s := range axes {
817 if !s.Set {
818 continue
819 }
820 if s.Stop {
821 if v >= 5 {
822 p.SendAxisStop(a.TimeMsec, uint32(axis))
823 }
824 continue
825 }
826 value := s.Value
827 if a.Source == ports.AxisWheel && s.V120 != 0 {
828 switch {
829 case v >= 8:
830 p.SendAxisValue120(uint32(axis), s.V120)
831 default:
832 value = values[axis]
833 if v >= 5 {
834 p.SendAxisDiscrete(uint32(axis), steps[axis])
835 }
836 }
837 }
838 p.SendAxis(a.TimeMsec, uint32(axis), server.FixedFromFloat(value))
839 }
840 pointerFrame(p)
841}
842
843func pointerFrame(p *wayland.Pointer) {
844 if p.Version() >= 5 {
845 p.SendFrame()
846 }
847}
848func (s *Server) windowPointers(w *window) []*wayland.Pointer {
849 if w == nil || !w.mapped || !w.xdg.resource.Resource.Alive() {
850 return nil
851 }
852 return s.clientPointers(w.xdg.resource.Client())
853}
854
855func (s *Server) layerPointers(l *layerSurface) []*wayland.Pointer {
856 if l == nil || !l.mapped || !l.resource.Resource.Alive() {
857 return nil
858 }
859 return s.clientPointers(l.resource.Client())
860}
861
862func (s *Server) clientPointers(c server.Client) []*wayland.Pointer {
863 var result []*wayland.Pointer
864 for _, p := range s.pointers[c] {
865 if p.Resource.Alive() {
866 result = append(result, p)
867 }
868 }
869 return result
870}
871func (s *Server) changePointerFocus(id ports.WindowID, x, y float64) {
872 if id == s.pointerFocus {
873 return
874 }
875 s.wheelRest, s.wheelHeld = [2]int32{}, [2]float64{}
876 // The new client sets its own cursor on enter; until then, the arrow.
877 s.cursorSurface = nil
878 s.setCursor(ports.CursorChange{})
879 if surface, pointers, _, _ := s.pointerSurface(s.pointerFocus, 0, 0); surface != nil {
880 for _, p := range pointers {
881 s.serial++
882 p.SendLeave(s.serial, surface)
883 pointerFrame(p)
884 }
885 }
886 s.pointerFocus = 0
887 if surface, pointers, sx, sy := s.pointerSurface(id, x, y); surface != nil {
888 s.pointerFocus, s.pointerX, s.pointerY = id, x, y
889 for _, p := range pointers {
890 s.serial++
891 p.SendEnter(s.serial, surface, server.FixedFromFloat(sx), server.FixedFromFloat(sy))
892 pointerFrame(p)
893 }
894 }
895 s.updateConstraint()
896}