authorclover caruso <git@paperclover.net> 2026-10-06 23:11:12-07:00
committerclover caruso <git@paperclover.net> 2026-10-07 01:55:40-07:00
log55bfeb62d8c4dcbd4df0c759d61ad49bdb9d9b49
treed9b62c51f41bac1c2cef9e27c95d224ce19d4d3c
parentf851910ec518002265217fc9cf84a45089dd89af
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU (~clover)

Fix Wayland cursor metadata negotiation and visibility

Accept compositor cursor metadata allocations up to 384 pixels while retaining the 128 pixel wire limit. Preserve cached shapes for position updates, hide invalid cursors, and encode empty sprites as transparent cursors. Validated with Debian 12 ASAN/UBSan and Xvfb integration tests, plus an SPA negotiation reproduction against Mutter’s fixed-size metadata. Assisted-by: gpt-6.1-sol

2 files changed, 26 insertions(+), 13 deletions(-)

guest/linux-tests.c+13-4
......@@ -40,10 +40,19 @@ int main(void) {
4040 pw_cursor(&buffer); assert(cursor_packet->len == SG_CURSOR_WORDS * 4);
4141 cursor->hotspot.x = 1; bitmap->offset = sizeof(*bitmap); pw_cursor(&buffer);
4242 assert(pointer[SG_POINTER_VISIBLE]);
43 cursor->id = 0; pw_cursor(&buffer); assert(pointer[SG_POINTER_VISIBLE]);
44 cursor->id = 7; bitmap->offset = 0; pw_cursor(&buffer); assert(!pointer[SG_POINTER_VISIBLE]);
45 cursor->bitmap_offset = 0; pw_cursor(&buffer); assert(!pointer[SG_POINTER_VISIBLE]);
46 cursor->bitmap_offset = sizeof(*cursor); bitmap->format = 0; pw_cursor(&buffer); assert(!pointer[SG_POINTER_VISIBLE]);
43 length = cursor_packet->len;
44 cursor->id = 0; pw_cursor(&buffer); assert(!pointer[SG_POINTER_VISIBLE] && cursor_packet->len == length);
45 cursor->id = 7; cursor->bitmap_offset = 0; pw_cursor(&buffer);
46 assert(pointer[SG_POINTER_VISIBLE] && cursor_packet->len == length);
47 cursor->bitmap_offset = sizeof(*cursor); bitmap->offset = 0; bitmap->format = 0;
48 pw_cursor(&buffer);
49 assert(word(cursor_packet->data + SG_CURSOR_WIDTH * 4) == 1);
50 assert(word(cursor_packet->data + SG_CURSOR_HEIGHT * 4) == 1);
51 assert(!memcmp(cursor_packet->data + SG_CURSOR_WORDS * 4 + 4, (uint8_t[4]){0}, 4));
52 length = cursor_packet->len; cursor->bitmap_offset = 0; pw_cursor(&buffer);
53 assert(pointer[SG_POINTER_VISIBLE] && cursor_packet->len == length);
54 cursor->bitmap_offset = sizeof(*cursor); bitmap->offset = sizeof(*bitmap);
55 pw_cursor(&buffer); assert(cursor_packet->len == length);
4756 bitmap->format = SPA_VIDEO_FORMAT_BGRA; bitmap->offset = sizeof(*bitmap); pw_cursor(&buffer); assert(pointer[SG_POINTER_VISIBLE]);
4857 uint8_t drain[4096]; while (read(sockets[1], drain, sizeof(drain)) > 0) {}
4958 g_byte_array_set_size(cursor_packet, 0);
guest/linux.c+13-9
......@@ -317,17 +317,19 @@ static void pw_cursor(struct spa_buffer *buffer) {
317317 struct spa_meta *meta = spa_buffer_find_meta(buffer, SPA_META_Cursor);
318318 if (!meta || meta->size < sizeof(struct spa_meta_cursor)) return;
319319 struct spa_meta_cursor cursor; memcpy(&cursor, meta->data, sizeof(cursor));
320 position(cursor.position.x, cursor.position.y, cursor.id != 0);
320321 if (!cursor.id) return;
321 position(cursor.position.x, cursor.position.y, pointer[SG_POINTER_VISIBLE]);
322322 if (!cursor.bitmap_offset) return;
323323 if (cursor.bitmap_offset < sizeof(cursor) || cursor.bitmap_offset > meta->size - sizeof(struct spa_meta_bitmap)) return;
324324 struct spa_meta_bitmap bitmap; memcpy(&bitmap, (uint8_t *)meta->data + cursor.bitmap_offset, sizeof(bitmap));
325 if (!bitmap.offset || !bitmap.size.width || !bitmap.size.height) {
326 cursor_shape(1, 1, 0, 0, (uint8_t[4]){0}); return;
327 }
325328 if (!bitmap.format) return;
326 if (!bitmap.offset) { position(cursor.position.x, cursor.position.y, 0); return; }
327329 uint32_t w = bitmap.size.width, h = bitmap.size.height;
328330 size_t available = meta->size - cursor.bitmap_offset;
329 if (!w || !h || w > SG_MAX_CURSOR_SIZE || h > SG_MAX_CURSOR_SIZE || bitmap.stride < (int32_t)w * 4 || bitmap.offset < sizeof(bitmap) || bitmap.offset > available || (size_t)(h - 1) * bitmap.stride + w * 4 > available - bitmap.offset) {
330 position(cursor.position.x, cursor.position.y, 1); cursor_shape(0, 0, 0, 0, NULL); return;
331 if (w > SG_MAX_CURSOR_SIZE || h > SG_MAX_CURSOR_SIZE || bitmap.stride < (int32_t)w * 4 || bitmap.offset < sizeof(bitmap) || bitmap.offset > available || (size_t)(h - 1) * bitmap.stride + w * 4 > available - bitmap.offset) {
332 cursor_shape(0, 0, 0, 0, NULL); return;
331333 }
332334 unsigned offsets[4];
333335 switch (bitmap.format) {
......@@ -335,21 +337,19 @@ static void pw_cursor(struct spa_buffer *buffer) {
335337 case SPA_VIDEO_FORMAT_BGRA: offsets[0] = 2; offsets[1] = 1; offsets[2] = 0; offsets[3] = 3; break;
336338 case SPA_VIDEO_FORMAT_ARGB: offsets[0] = 1; offsets[1] = 2; offsets[2] = 3; offsets[3] = 0; break;
337339 case SPA_VIDEO_FORMAT_ABGR: offsets[0] = 3; offsets[1] = 2; offsets[2] = 1; offsets[3] = 0; break;
338 default: position(cursor.position.x, cursor.position.y, 1); cursor_shape(0, 0, 0, 0, NULL); return;
340 default: cursor_shape(0, 0, 0, 0, NULL); return;
339341 }
340342 uint8_t rgba[SG_MAX_CURSOR_SIZE * SG_MAX_CURSOR_SIZE * 4];
341343 const uint8_t *pixels = (uint8_t *)meta->data + cursor.bitmap_offset + bitmap.offset;
342 int visible = 0;
343344 for (uint32_t y = 0; y < h; ++y) for (uint32_t x = 0; x < w; ++x) {
344345 const uint8_t *src = pixels + (size_t)y * bitmap.stride + x * 4;
345346 uint8_t *dst = rgba + ((size_t)y * w + x) * 4;
346 unsigned alpha = src[offsets[3]]; dst[3] = (uint8_t)alpha; visible |= alpha != 0;
347 unsigned alpha = src[offsets[3]]; dst[3] = (uint8_t)alpha;
347348 for (unsigned c = 0; c < 3; ++c) {
348349 unsigned value = alpha ? (src[offsets[c]] * 255u + alpha / 2) / alpha : 0;
349350 dst[c] = (uint8_t)(value > 255 ? 255 : value);
350351 }
351352 }
352 position(cursor.position.x, cursor.position.y, visible);
353353 cursor_shape(w, h, cursor.hotspot.x, cursor.hotspot.y, rgba);
354354}
355355
......@@ -401,7 +401,11 @@ static void pw_format(void *unused, uint32_t id, const struct spa_pod *param) {
401401 SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1 << SPA_DATA_MemPtr) | (1 << SPA_DATA_MemFd)));
402402 params[1] = spa_pod_builder_add_object(&builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
403403 SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Cursor),
404 SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_cursor) + sizeof(struct spa_meta_bitmap) + SG_MAX_CURSOR_SIZE * SG_MAX_CURSOR_SIZE * 4));
404 /* Compositors can require a larger allocation than the transmitted cursor limit. */
405 SPA_PARAM_META_size, SPA_POD_CHOICE_RANGE_Int(
406 sizeof(struct spa_meta_cursor) + sizeof(struct spa_meta_bitmap) + SG_MAX_CURSOR_SIZE * SG_MAX_CURSOR_SIZE * 4,
407 sizeof(struct spa_meta_cursor) + sizeof(struct spa_meta_bitmap) + 4,
408 sizeof(struct spa_meta_cursor) + sizeof(struct spa_meta_bitmap) + 384 * 384 * 4));
405409 params[2] = spa_pod_builder_add_object(&builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
406410 SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoTransform), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_videotransform)));
407411 pw_stream_update_params(pw_stream, params, 3);