1//go:build linux
2
3package wayland
4
5import (
6 "fmt"
7 "github.com/bnema/wlturbo/protocol/linuxdmabuf"
8 "net"
9 "time"
10)
11
12type EventKind uint8
13
14const (
15 ConfigureSize EventKind = iota + 1
16 ConfigureSerial
17 PreferredScale
18 BufferScale
19 CloseEvent
20 FrameEvent
21 BufferRelease
22 TransportError
23 SeatCapabilities
24 SeatRemoved
25 InputFrame
26 InputKeymap
27 InputFocusIn
28 InputFocusOut
29 InputKey
30 InputModifiers
31 InputRepeatInfo
32 FeedbackError
33 FeedbackMainDevice
34 FeedbackTranche
35 FeedbackFormats
36 ClipboardInput
37 IMEInput
38)
39
40type Event struct {
41 Kind EventKind
42 Width, Height int32
43 Serial, Scale uint32
44 Factor int32
45 BufferID uint64
46 Err error
47 Device uint64
48 Tranche, Formats []linuxdmabuf.FormatEntry
49 Inputs []Input
50 Clipboard ClipboardEvent
51 IME IMEEvent
52 Capabilities, Format, Size, Code uint32
53 FD int // owned by ApplyInput or CloseEventFD when Kind == InputKeymap
54 Pressed bool
55 Mods [4]uint32
56 Rate, Delay int32
57}
58
59// StartReader launches exactly one blocking Wayland reader. Registered callbacks
60// only send typed values to Events; Apply is called by the session loop alone.
61func (w *Window) StartReader() <-chan Event {
62 w.readerOnce.Do(func() {
63 w.readerStarted.Store(true)
64 w.readerDone = make(chan struct{})
65 go func() {
66 defer close(w.readerDone)
67 for {
68 err := w.Display.Dispatch()
69 if err != nil {
70 select {
71 case <-w.readerStop:
72 return
73 case w.events <- Event{Kind: TransportError, Err: err}:
74 return
75 }
76 }
77 }
78 }()
79 })
80 return w.events
81}
82
83// Events exposes the reader's typed event queue to the session owner.
84func (w *Window) Events() <-chan Event { return w.events }
85
86// PostClipboard and PostIME enqueue transport callbacks for the owner loop.
87func (w *Window) PostClipboard(ev ClipboardEvent) { w.post(Event{Kind: ClipboardInput, Clipboard: ev}) }
88func (w *Window) PostIME(ev IMEEvent) { w.post(Event{Kind: IMEInput, IME: ev}) }
89
90func (w *Window) post(ev Event) {
91 select {
92 case <-w.readerStop:
93 return
94 case w.events <- ev:
95 }
96}
97
98// postFD closes an undeliverable keymap descriptor rather than leaking it.
99func (w *Window) postFD(ev Event) {
100 select {
101 case <-w.readerStop:
102 CloseEventFD(ev)
103 case w.events <- ev:
104 }
105}
106
107// BufferReleased is safe to call from the reader callback: it never touches
108// Pool, Session, or Vulkan. The serial loop records the optional event.
109func (w *Window) BufferReleased(id uint64) {
110 w.post(Event{Kind: BufferRelease, BufferID: id})
111}
112func (w *Window) Apply(ev Event) error {
113 switch ev.Kind {
114 case ConfigureSize:
115 if ev.Width > 0 && ev.Height > 0 && (w.Width != ev.Width || w.Height != ev.Height) {
116 w.Width, w.Height, w.Dirty = ev.Width, ev.Height, true
117 }
118 case ConfigureSerial:
119 if err := w.XdgSurface.AckConfigure(ev.Serial); err != nil {
120 return err
121 }
122 w.Configured, w.FrameReady = true, true
123 case PreferredScale:
124 if ev.Scale == 0 {
125 return fmt.Errorf("invalid preferred scale zero")
126 }
127 if ev.Scale%120 != 0 && w.Viewport == nil {
128 return &CapabilityError{Name: "wp_viewporter", Cause: fmt.Errorf("fractional scale %d/120 requires viewporter", ev.Scale)}
129 }
130 if scale := float64(ev.Scale) / 120; w.Scale != scale {
131 w.Scale = scale
132 w.Dirty = true
133 }
134 case BufferScale:
135 if w.Fractional == nil && ev.Factor > 0 && w.Scale != float64(ev.Factor) {
136 w.Scale = float64(ev.Factor)
137 w.Dirty = true
138 }
139 case CloseEvent:
140 w.Closed = true
141 case FrameEvent:
142 w.FrameReady = true
143 case FeedbackError:
144 if w.feedbackErr == nil {
145 w.feedbackErr = ev.Err
146 }
147 return ev.Err
148 case FeedbackMainDevice:
149 w.MainDevice = ev.Device
150 case FeedbackTranche:
151 w.Tranches = append(w.Tranches, ev.Tranche)
152 case FeedbackFormats:
153 w.Formats = ev.Formats
154 case SeatCapabilities, SeatRemoved:
155 return w.ApplySeat(ev)
156 case ClipboardInput, IMEInput:
157 // These are applied by the session owner after Window.Apply.
158 case InputFrame, InputKeymap, InputFocusIn, InputFocusOut, InputKey, InputModifiers, InputRepeatInfo:
159 if w.inputAdapter == nil {
160 var err error
161 w.inputAdapter, err = NewInputAdapter("")
162 if err != nil {
163 CloseEventFD(ev)
164 return err
165 }
166 }
167 inputs, err := w.inputAdapter.ApplyInput(ev, time.Now())
168 w.queueInput(inputs)
169 return err
170 case BufferRelease: // consumed by the session owner; no pixel ownership implication
171 case TransportError:
172 if ev.Err != nil {
173 return ev.Err
174 }
175 return net.ErrClosed
176 default:
177 return fmt.Errorf("unknown Wayland event %d", ev.Kind)
178 }
179 return nil
180}