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) {...@@ -40,10 +40,19 @@ int main(void) {
40 pw_cursor(&buffer); assert(cursor_packet->len == SG_CURSOR_WORDS * 4);40 pw_cursor(&buffer); assert(cursor_packet->len == SG_CURSOR_WORDS * 4);
41 cursor->hotspot.x = 1; bitmap->offset = sizeof(*bitmap); pw_cursor(&buffer);41 cursor->hotspot.x = 1; bitmap->offset = sizeof(*bitmap); pw_cursor(&buffer);
42 assert(pointer[SG_POINTER_VISIBLE]);42 assert(pointer[SG_POINTER_VISIBLE]);
43 cursor->id = 0; pw_cursor(&buffer); assert(pointer[SG_POINTER_VISIBLE]);43 length = cursor_packet->len;
44 cursor->id = 7; bitmap->offset = 0; pw_cursor(&buffer); assert(!pointer[SG_POINTER_VISIBLE]);44 cursor->id = 0; pw_cursor(&buffer); assert(!pointer[SG_POINTER_VISIBLE] && cursor_packet->len == length);
45 cursor->bitmap_offset = 0; pw_cursor(&buffer); assert(!pointer[SG_POINTER_VISIBLE]);45 cursor->id = 7; cursor->bitmap_offset = 0; pw_cursor(&buffer);
46 cursor->bitmap_offset = sizeof(*cursor); bitmap->format = 0; pw_cursor(&buffer); assert(!pointer[SG_POINTER_VISIBLE]);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);
47 bitmap->format = SPA_VIDEO_FORMAT_BGRA; bitmap->offset = sizeof(*bitmap); pw_cursor(&buffer); assert(pointer[SG_POINTER_VISIBLE]);56 bitmap->format = SPA_VIDEO_FORMAT_BGRA; bitmap->offset = sizeof(*bitmap); pw_cursor(&buffer); assert(pointer[SG_POINTER_VISIBLE]);
48 uint8_t drain[4096]; while (read(sockets[1], drain, sizeof(drain)) > 0) {}57 uint8_t drain[4096]; while (read(sockets[1], drain, sizeof(drain)) > 0) {}
49 g_byte_array_set_size(cursor_packet, 0);58 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) {...@@ -317,17 +317,19 @@ static void pw_cursor(struct spa_buffer *buffer) {
317 struct spa_meta *meta = spa_buffer_find_meta(buffer, SPA_META_Cursor);317 struct spa_meta *meta = spa_buffer_find_meta(buffer, SPA_META_Cursor);
318 if (!meta || meta->size < sizeof(struct spa_meta_cursor)) return;318 if (!meta || meta->size < sizeof(struct spa_meta_cursor)) return;
319 struct spa_meta_cursor cursor; memcpy(&cursor, meta->data, sizeof(cursor));319 struct spa_meta_cursor cursor; memcpy(&cursor, meta->data, sizeof(cursor));
320 position(cursor.position.x, cursor.position.y, cursor.id != 0);
320 if (!cursor.id) return;321 if (!cursor.id) return;
321 position(cursor.position.x, cursor.position.y, pointer[SG_POINTER_VISIBLE]);
322 if (!cursor.bitmap_offset) return;322 if (!cursor.bitmap_offset) return;
323 if (cursor.bitmap_offset < sizeof(cursor) || cursor.bitmap_offset > meta->size - sizeof(struct spa_meta_bitmap)) return;323 if (cursor.bitmap_offset < sizeof(cursor) || cursor.bitmap_offset > meta->size - sizeof(struct spa_meta_bitmap)) return;
324 struct spa_meta_bitmap bitmap; memcpy(&bitmap, (uint8_t *)meta->data + cursor.bitmap_offset, sizeof(bitmap));324 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 }
325 if (!bitmap.format) return;328 if (!bitmap.format) return;
326 if (!bitmap.offset) { position(cursor.position.x, cursor.position.y, 0); return; }
327 uint32_t w = bitmap.size.width, h = bitmap.size.height;329 uint32_t w = bitmap.size.width, h = bitmap.size.height;
328 size_t available = meta->size - cursor.bitmap_offset;330 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) {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) {
330 position(cursor.position.x, cursor.position.y, 1); cursor_shape(0, 0, 0, 0, NULL); return;332 cursor_shape(0, 0, 0, 0, NULL); return;
331 }333 }
332 unsigned offsets[4];334 unsigned offsets[4];
333 switch (bitmap.format) {335 switch (bitmap.format) {
...@@ -335,21 +337,19 @@ static void pw_cursor(struct spa_buffer *buffer) {...@@ -335,21 +337,19 @@ static void pw_cursor(struct spa_buffer *buffer) {
335 case SPA_VIDEO_FORMAT_BGRA: offsets[0] = 2; offsets[1] = 1; offsets[2] = 0; offsets[3] = 3; break;337 case SPA_VIDEO_FORMAT_BGRA: offsets[0] = 2; offsets[1] = 1; offsets[2] = 0; offsets[3] = 3; break;
336 case SPA_VIDEO_FORMAT_ARGB: offsets[0] = 1; offsets[1] = 2; offsets[2] = 3; offsets[3] = 0; break;338 case SPA_VIDEO_FORMAT_ARGB: offsets[0] = 1; offsets[1] = 2; offsets[2] = 3; offsets[3] = 0; break;
337 case SPA_VIDEO_FORMAT_ABGR: offsets[0] = 3; offsets[1] = 2; offsets[2] = 1; offsets[3] = 0; break;339 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;
339 }341 }
340 uint8_t rgba[SG_MAX_CURSOR_SIZE * SG_MAX_CURSOR_SIZE * 4];342 uint8_t rgba[SG_MAX_CURSOR_SIZE * SG_MAX_CURSOR_SIZE * 4];
341 const uint8_t *pixels = (uint8_t *)meta->data + cursor.bitmap_offset + bitmap.offset;343 const uint8_t *pixels = (uint8_t *)meta->data + cursor.bitmap_offset + bitmap.offset;
342 int visible = 0;
343 for (uint32_t y = 0; y < h; ++y) for (uint32_t x = 0; x < w; ++x) {344 for (uint32_t y = 0; y < h; ++y) for (uint32_t x = 0; x < w; ++x) {
344 const uint8_t *src = pixels + (size_t)y * bitmap.stride + x * 4;345 const uint8_t *src = pixels + (size_t)y * bitmap.stride + x * 4;
345 uint8_t *dst = rgba + ((size_t)y * w + x) * 4;346 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;
347 for (unsigned c = 0; c < 3; ++c) {348 for (unsigned c = 0; c < 3; ++c) {
348 unsigned value = alpha ? (src[offsets[c]] * 255u + alpha / 2) / alpha : 0;349 unsigned value = alpha ? (src[offsets[c]] * 255u + alpha / 2) / alpha : 0;
349 dst[c] = (uint8_t)(value > 255 ? 255 : value);350 dst[c] = (uint8_t)(value > 255 ? 255 : value);
350 }351 }
351 }352 }
352 position(cursor.position.x, cursor.position.y, visible);
353 cursor_shape(w, h, cursor.hotspot.x, cursor.hotspot.y, rgba);353 cursor_shape(w, h, cursor.hotspot.x, cursor.hotspot.y, rgba);
354}354}
355355
...@@ -401,7 +401,11 @@ static void pw_format(void *unused, uint32_t id, const struct spa_pod *param) {...@@ -401,7 +401,11 @@ static void pw_format(void *unused, uint32_t id, const struct spa_pod *param) {
401 SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1 << SPA_DATA_MemPtr) | (1 << SPA_DATA_MemFd)));401 SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1 << SPA_DATA_MemPtr) | (1 << SPA_DATA_MemFd)));
402 params[1] = spa_pod_builder_add_object(&builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,402 params[1] = spa_pod_builder_add_object(&builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
403 SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Cursor),403 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));
405 params[2] = spa_pod_builder_add_object(&builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,409 params[2] = spa_pod_builder_add_object(&builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
406 SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoTransform), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_videotransform)));410 SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoTransform), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_videotransform)));
407 pw_stream_update_params(pw_stream, params, 3);411 pw_stream_update_params(pw_stream, params, 3);