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 }