lib/wayland/src/runtime/dispatch.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const sys = @import("sys");
3 const wayland = @import("../root.zig");
4 const event = @import("event.zig");
5 const lifecycle = @import("lifecycle.zig");
6 const object = @import("object.zig");
7 const semantic = @import("semantic.zig");
8
9 const Buffered = union(enum) {
10 waiting,
11 discarded,
12 event: event.EventView,
13 };
14
15 pub fn step(client: anytype) !event.Step {
16 while (true) {
17 const dispatched = dispatchBuffered(client) catch |err| {
18 if (err == error.OutOfMemory) return err;
19 client.terminal_error = err;
20 return err;
21 };
22 switch (dispatched) {
23 .event => |item| return .{ .event = item },
24 .discarded => continue,
25 .waiting => {},
26 }
27
28 const received = client.transport.receive() catch |err| {
29 client.terminal_error = err;
30 return err;
31 };
32 switch (received) {
33 .data => {},
34 .pending => return .pending,
35 .closed => {
36 if (client.transport.inbox.queuedByteCount() != 0 or
37 client.transport.inbox.queuedDescriptorCount() != 0)
38 {
39 client.terminal_error = error.TruncatedStream;
40 return error.TruncatedStream;
41 }
42 client.terminal_error = error.ConnectionClosed;
43 return .closed;
44 },
45 }
46 }
47 }
48
49 fn dispatchBuffered(client: anytype) !Buffered {
50 const frame = (try client.transport.inbox.peek()) orelse return .waiting;
51 const entry = try client.objects.require(frame.header.object_id);
52 const metadata = entry.interface.event(frame.header.opcode) orelse {
53 return error.UnknownEventOpcode;
54 };
55 if (!metadata.supportedBy(entry.version)) return error.UnsupportedEventVersion;
56 const descriptors = client.transport.inbox.peekDescriptors(metadata.descriptor_count) orelse {
57 return .waiting;
58 };
59 try wayland.protocol.value.validate.message(metadata, frame.payload, descriptors);
60
61 switch (entry.state) {
62 .retired_request => {
63 discardFrame(client, metadata.descriptor_count);
64 return .discarded;
65 },
66 .retired_event => return error.UnknownObject,
67 .live => {},
68 }
69
70 if (entry.interface == try client.catalog.require("wl_display")) {
71 return .{ .event = try dispatchDisplay(
72 client,
73 frame.header.opcode,
74 frame.payload,
75 descriptors,
76 ) };
77 }
78 if (entry.interface == try client.catalog.require("wl_registry")) {
79 return .{ .event = try dispatchRegistry(
80 client,
81 frame.header.object_id,
82 frame.header.opcode,
83 frame.payload,
84 descriptors,
85 ) };
86 }
87 if (entry.interface == try client.catalog.require("wl_callback")) {
88 return .{ .event = try dispatchCallback(
89 client,
90 frame.header.object_id,
91 frame.header.opcode,
92 frame.payload,
93 descriptors,
94 entry,
95 ) };
96 }
97 return .{ .event = try dispatchRouted(client, frame, descriptors, entry, metadata) };
98 }
99
100 fn discardFrame(client: anytype, descriptor_count: usize) void {
101 for (0..descriptor_count) |_| {
102 sys.fd.close(client.transport.inbox.takeDescriptor().?);
103 }
104 client.transport.inbox.consume() catch unreachable;
105 }
106
107 fn dispatchDisplay(
108 client: anytype,
109 opcode: u16,
110 payload: []const u8,
111 descriptors: []const sys.fd.Descriptor,
112 ) !event.EventView {
113 const decoded = try wayland.protocol.bootstrap.display.decodeEvent(
114 opcode,
115 payload,
116 descriptors,
117 );
118 switch (decoded) {
119 .delete_id => |item| {
120 const entry = try client.objects.require(item.id);
121 if (entry.origin != .client or entry.state == .live) {
122 return error.ObjectStillLive;
123 }
124 try client.ids.acknowledgeDelete(item.id);
125 _ = client.objects.removeRetiredClient(item.id) catch unreachable;
126 client.transport.inbox.consume() catch unreachable;
127 return .{ .delete_id = item.id };
128 },
129 .fatal => |item| {
130 try client.event_storage.admit(item.message.len, 0);
131 const message = client.event_storage.storePayloadAssumeCapacity(item.message);
132 const fatal: event.FatalView = .{
133 .object_id = item.object_id.raw,
134 .code = item.code,
135 .message = message,
136 };
137 client.fatal_state = fatal;
138 client.terminal_error = error.FatalProtocol;
139 client.transport.inbox.consume() catch unreachable;
140 return .{ .fatal = fatal };
141 },
142 }
143 }
144
145 fn dispatchRegistry(
146 client: anytype,
147 registry_id: u32,
148 opcode: u16,
149 payload: []const u8,
150 descriptors: []const sys.fd.Descriptor,
151 ) !event.EventView {
152 const decoded = try wayland.protocol.bootstrap.registry.decodeEvent(
153 opcode,
154 payload,
155 descriptors,
156 );
157 switch (decoded) {
158 .global => |item| {
159 const admission = try client.globals.prepareAdd(
160 registry_id,
161 item.name,
162 item.interface,
163 item.version,
164 );
165 const global = client.globals.commitAdd(admission, item.interface);
166 client.transport.inbox.consume() catch unreachable;
167 return .{ .global = .{
168 .registry_id = global.registry_id,
169 .name = global.name,
170 .interface = global.interface,
171 .version = global.version,
172 } };
173 },
174 .global_remove => |item| {
175 try client.globals.remove(registry_id, item.name);
176 client.transport.inbox.consume() catch unreachable;
177 return .{ .global_remove = .{
178 .registry_id = registry_id,
179 .name = item.name,
180 } };
181 },
182 }
183 }
184
185 fn dispatchCallback(
186 client: anytype,
187 callback_id: u32,
188 opcode: u16,
189 payload: []const u8,
190 descriptors: []const sys.fd.Descriptor,
191 entry: object.Entry,
192 ) !event.EventView {
193 const item = try wayland.protocol.bootstrap.callback.decodeEvent(opcode, payload, descriptors);
194 lifecycle.retire(&client.ids, &client.objects, callback_id, entry, .retired_event);
195 client.transport.inbox.consume() catch unreachable;
196 return .{ .callback_done = .{
197 .callback_id = callback_id,
198 .data = item.data,
199 } };
200 }
201
202 fn dispatchRouted(
203 client: anytype,
204 frame: wayland.stream.Frame,
205 descriptors: []const sys.fd.Descriptor,
206 entry: object.Entry,
207 metadata: *const wayland.protocol.schema.Message,
208 ) !event.EventView {
209 try semantic.validateEvent(
210 &client.objects,
211 client.catalog,
212 entry,
213 metadata,
214 frame.payload,
215 descriptors,
216 &client.event_creations,
217 );
218 try client.event_storage.admit(frame.payload.len, descriptors.len);
219 semantic.commit(&client.objects, &client.event_creations);
220 const stored = client.event_storage.storeRoutedAssumeCapacity(
221 frame.payload,
222 descriptors,
223 );
224 for (stored.descriptors) |descriptor| {
225 std.debug.assert(descriptor == client.transport.inbox.takeDescriptor().?);
226 }
227 client.transport.inbox.consume() catch unreachable;
228 if (metadata.destructor) {
229 lifecycle.retire(
230 &client.ids,
231 &client.objects,
232 frame.header.object_id,
233 entry,
234 .retired_event,
235 );
236 }
237 return .{ .routed = .{
238 .object_id = frame.header.object_id,
239 .interface = entry.interface,
240 .version = entry.version,
241 .metadata = metadata,
242 .payload = stored.payload,
243 .descriptors = stored.descriptors,
244 } };
245 }