lib/wayland/src/runtime/request.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const sys = @import("sys");
3 const wayland = @import("../root.zig");
4 const catalog_model = @import("catalog.zig");
5 const object = @import("object.zig");
6
7 pub const NewId = union(enum) {
8 fixed,
9 dynamic: Dynamic,
10
11 pub const Dynamic = struct {
12 interface: []const u8,
13 version: u32,
14 };
15 };
16
17 pub const Value = union(enum) {
18 int: i32,
19 uint: u32,
20 fixed: wayland.protocol.value.Fixed,
21 string: ?[]const u8,
22 object: ?u32,
23 new_id: NewId,
24 array: []const u8,
25 descriptor_owned: sys.fd.Descriptor,
26 };
27
28 pub const Prepared = struct {
29 encoded: wayland.protocol.value.Encoded,
30 created_ids: []u32,
31 };
32
33 pub fn prepare(
34 ids: *wayland.ids.Pool,
35 objects: *object.Table,
36 catalog: catalog_model.Catalog,
37 encoder: *wayland.protocol.value.Encoder,
38 parent: object.Entry,
39 metadata: *const wayland.protocol.schema.Message,
40 values: []const Value,
41 created_ids: []u32,
42 ) !Prepared {
43 if (values.len != metadata.arguments.len) return error.ArgumentCountMismatch;
44 if (created_ids.len != metadata.newIdCount()) return error.CreatedIdCountMismatch;
45 encoder.reset();
46 errdefer encoder.reset();
47 var created_count: usize = 0;
48 errdefer rollback(ids, objects, created_ids[0..created_count]);
49
50 for (metadata.arguments, values) |argument, value| switch (argument.kind) {
51 .int => try encoder.signed(switch (value) {
52 .int => |item| item,
53 else => return error.ArgumentTypeMismatch,
54 }),
55 .uint => try encoder.unsigned(switch (value) {
56 .uint => |item| item,
57 else => return error.ArgumentTypeMismatch,
58 }),
59 .fixed => try encoder.fixed(switch (value) {
60 .fixed => |item| item,
61 else => return error.ArgumentTypeMismatch,
62 }),
63 .string => {
64 const item = switch (value) {
65 .string => |item| item,
66 else => return error.ArgumentTypeMismatch,
67 };
68 if (argument.nullable) {
69 try encoder.optionalString(item);
70 } else {
71 try encoder.string(item orelse return error.NullArgument);
72 }
73 },
74 .object => {
75 const id = switch (value) {
76 .object => |item| item,
77 else => return error.ArgumentTypeMismatch,
78 };
79 if (id) |present| {
80 const referenced = try objects.requireLive(present);
81 if (argument.interface) |name| {
82 if (referenced.interface != try catalog.require(name)) {
83 return error.ObjectInterfaceMismatch;
84 }
85 }
86 try encoder.object(try .init(present));
87 } else {
88 if (!argument.nullable) return error.NullArgument;
89 try encoder.optionalObject(null);
90 }
91 },
92 .new_id => {
93 const specification = switch (value) {
94 .new_id => |item| item,
95 else => return error.ArgumentTypeMismatch,
96 };
97 const interface, const version = if (argument.interface) |name| fixed: {
98 switch (specification) {
99 .fixed => {},
100 else => return error.ArgumentTypeMismatch,
101 }
102 const interface = try catalog.require(name);
103 break :fixed .{ interface, @min(parent.version, interface.version) };
104 } else dynamic: {
105 const dynamic = switch (specification) {
106 .dynamic => |item| item,
107 else => return error.ArgumentTypeMismatch,
108 };
109 const interface = try catalog.require(dynamic.interface);
110 if (dynamic.version == 0 or dynamic.version > interface.version) {
111 return error.InvalidVersion;
112 }
113 break :dynamic .{ interface, dynamic.version };
114 };
115 const id = try ids.allocate();
116 var registered = false;
117 errdefer if (!registered) ids.abandon(id) catch unreachable;
118 try objects.addClient(id, interface, version);
119 created_ids[created_count] = id;
120 created_count += 1;
121 registered = true;
122 if (argument.interface != null) {
123 try encoder.newId(try .init(id));
124 } else {
125 try encoder.dynamicNewId(interface.name, version, try .init(id));
126 }
127 },
128 .array => try encoder.array(switch (value) {
129 .array => |item| item,
130 else => return error.ArgumentTypeMismatch,
131 }),
132 .fd => try encoder.descriptorBorrowed(switch (value) {
133 .descriptor_owned => |item| item,
134 else => return error.ArgumentTypeMismatch,
135 }),
136 };
137
138 std.debug.assert(created_count == created_ids.len);
139 return .{
140 .encoded = try encoder.finish(metadata),
141 .created_ids = created_ids,
142 };
143 }
144
145 pub fn rollback(
146 ids: *wayland.ids.Pool,
147 objects: *object.Table,
148 created_ids: []const u32,
149 ) void {
150 var index = created_ids.len;
151 while (index != 0) {
152 index -= 1;
153 _ = objects.removeLive(created_ids[index]) catch unreachable;
154 ids.abandon(created_ids[index]) catch unreachable;
155 }
156 }
157
158 test "fixed constructors inherit negotiated parent versions transactionally" {
159 const catalog = catalog_model.Catalog.standard();
160 var ids = try wayland.ids.Pool.init(std.testing.allocator, wayland.ids.default_capacity);
161 defer ids.deinit();
162 var objects = try object.Table.init(std.testing.allocator, object.default_capacity);
163 defer objects.deinit();
164 try objects.addDisplay(try catalog.require("wl_display"));
165 const compositor_id = try ids.allocate();
166 try objects.addClient(compositor_id, try catalog.require("wl_compositor"), 4);
167 var encoder = try wayland.protocol.value.Encoder.init(
168 std.testing.allocator,
169 wayland.protocol.value.default_encoder_capacity,
170 );
171 defer encoder.deinit();
172
173 var created: [1]u32 = undefined;
174 const compositor = try objects.requireLive(compositor_id);
175 const prepared = try prepare(
176 &ids,
177 &objects,
178 catalog,
179 &encoder,
180 compositor,
181 compositor.interface.request(0).?,
182 &.{.{ .new_id = .fixed }},
183 &created,
184 );
185 try std.testing.expectEqualStrings(
186 "wl_surface",
187 (try objects.requireLive(created[0])).interface.name,
188 );
189 try std.testing.expectEqual(@as(u32, 4), (try objects.requireLive(created[0])).version);
190 try wayland.protocol.value.validate.message(
191 prepared.encoded.metadata,
192 prepared.encoded.payload,
193 prepared.encoded.descriptors,
194 );
195 rollback(&ids, &objects, &created);
196 try std.testing.expectEqual(created[0], try ids.allocate());
197 }
198
199 test "object arguments require live objects of the declared interface" {
200 const catalog = catalog_model.Catalog.standard();
201 var ids = try wayland.ids.Pool.init(std.testing.allocator, wayland.ids.default_capacity);
202 defer ids.deinit();
203 var objects = try object.Table.init(std.testing.allocator, object.default_capacity);
204 defer objects.deinit();
205 try objects.addDisplay(try catalog.require("wl_display"));
206 const device_id = try ids.allocate();
207 try objects.addClient(device_id, try catalog.require("wl_data_device"), 3);
208 const wrong_id = try ids.allocate();
209 try objects.addClient(wrong_id, try catalog.require("wl_output"), 3);
210 var encoder = try wayland.protocol.value.Encoder.init(
211 std.testing.allocator,
212 wayland.protocol.value.default_encoder_capacity,
213 );
214 defer encoder.deinit();
215 var created: [0]u32 = .{};
216 const start_drag = (try objects.requireLive(device_id)).interface.request(0).?;
217 try std.testing.expectError(
218 error.ObjectInterfaceMismatch,
219 prepare(
220 &ids,
221 &objects,
222 catalog,
223 &encoder,
224 try objects.requireLive(device_id),
225 start_drag,
226 &.{
227 .{ .object = null },
228 .{ .object = wrong_id },
229 .{ .object = null },
230 .{ .uint = 1 },
231 },
232 &created,
233 ),
234 );
235 }
236
237 test "constructor capacity failure rolls back every staged client object" {
238 const catalog = catalog_model.Catalog.standard();
239 const id_capacity = try wayland.ids.Capacity.derive(.{ .client_id_count = 1 });
240 var ids = try wayland.ids.Pool.init(std.testing.allocator, id_capacity);
241 defer ids.deinit();
242 const object_capacity = try object.Capacity.derive(.{
243 .client_object_count = 1,
244 .server_object_count = 0,
245 });
246 var objects = try object.Table.init(std.testing.allocator, object_capacity);
247 defer objects.deinit();
248 try objects.addDisplay(try catalog.require("wl_display"));
249 var encoder = try wayland.protocol.value.Encoder.init(
250 std.testing.allocator,
251 wayland.protocol.value.default_encoder_capacity,
252 );
253 defer encoder.deinit();
254 const arguments = [_]wayland.protocol.schema.Argument{
255 .{
256 .name = "first",
257 .kind = .new_id,
258 .interface = "wl_callback",
259 .enumeration = null,
260 .nullable = false,
261 },
262 .{
263 .name = "second",
264 .kind = .new_id,
265 .interface = "wl_callback",
266 .enumeration = null,
267 .nullable = false,
268 },
269 };
270 const metadata: wayland.protocol.schema.Message = .{
271 .name = "two_constructors",
272 .opcode = 0,
273 .since = 1,
274 .deprecated_since = null,
275 .destructor = false,
276 .signature = "nn",
277 .descriptor_count = 0,
278 .minimum_payload_size = 8,
279 .arguments = &arguments,
280 };
281 var created: [2]u32 = undefined;
282
283 try std.testing.expectError(
284 error.ClientIdCapacityExceeded,
285 prepare(
286 &ids,
287 &objects,
288 catalog,
289 &encoder,
290 try objects.requireLive(wayland.ids.display_id),
291 &metadata,
292 &.{ .{ .new_id = .fixed }, .{ .new_id = .fixed } },
293 &created,
294 ),
295 );
296 try std.testing.expect(objects.get(wayland.ids.first_dynamic_id) == null);
297 try std.testing.expectEqual(
298 @as(u64, 1),
299 ids.status().client_id_capacity_rejection_count,
300 );
301 try std.testing.expectEqual(
302 @as(u64, 0),
303 objects.status().client_object_capacity_rejection_count,
304 );
305 try std.testing.expectEqual(wayland.ids.first_dynamic_id, try ids.allocate());
306 }