agent-desktop/crates
Lahfir 98f64fdeca feat(ffi): opaque list handles + image buffer length encapsulation (Unit 5)
Replaces every `(*mut T, count)` list-returning API with an opaque
handle and encapsulates AdImageBuffer's byte-buffer length. Closes
R8 and R22 from PR #22 review.

## Opaque list handles

Four new one-type-per-file opaque wrappers (no `#[repr(C)]` — cbindgen
auto-emits as `typedef struct AdFoo AdFoo;` forward declarations):

- crates/ffi/src/types/window_list.rs   — AdWindowList
- crates/ffi/src/types/app_list.rs      — AdAppList
- crates/ffi/src/types/surface_list.rs  — AdSurfaceList
- crates/ffi/src/types/notification_list.rs — AdNotificationList (used by Unit 8)

Each list owns its `Box<[AdXxxInfo]>`. Consumers walk through
`_count(list)`, `_get(list, index) -> *const AdXxxInfo` (null on OOB),
and free with `_free(list)` — the free walks the entries, releases
their interior C-strings, and drops the Box.

Rewritten signatures:

| Old                                                        | New                                                       |
|------------------------------------------------------------|-----------------------------------------------------------|
| ad_list_apps(adapter, \*\*apps, \*count)                   | ad_list_apps(adapter, \*\*list)                           |
| ad_list_windows(adapter, filter, focused, \*\*wins, \*count)| ad_list_windows(adapter, filter, focused, \*\*list)       |
| ad_list_surfaces(adapter, pid, \*\*sfs, \*count)           | ad_list_surfaces(adapter, pid, \*\*list)                  |
| ad_free_apps(apps, count)                                  | ad_app_list_free(list)                                    |
| ad_free_windows(wins, count)                               | ad_window_list_free(list)                                 |
| ad_free_surfaces(sfs, count)                               | ad_surface_list_free(list)                                |
| ad_free_window(win)   [for single AdWindowInfo]            | ad_release_window_fields(win)                             |

Count mismatches are impossible by construction — callers never see
the backing pointer or length.

## Image buffer encapsulation

crates/ffi/src/types/image_buffer.rs: dropped `#[repr(C)]`, private
`Box<[u8]>` data field, private width/height/format. Before, a C
caller who mutated `AdImageBuffer.data_len` triggered heap corruption
at free time; now the length is authoritative inside the Rust-owned
struct.

New accessors in crates/ffi/src/screenshot/accessors.rs:
- ad_image_buffer_data    -> *const u8
- ad_image_buffer_size    -> u64 (always matches the allocation)
- ad_image_buffer_width   -> u32
- ad_image_buffer_height  -> u32
- ad_image_buffer_format  -> AdImageFormat

ad_screenshot signature changed: `*mut *mut AdImageBuffer out` instead
of `*mut AdImageBuffer out`. ad_free_image renamed to
ad_image_buffer_free for consistency with the list-handle pattern.

All new files stay under 120 LOC, explicit `pub use` per type, no
wildcard imports, no inline `//` comments. `///` docs on every
public FFI export cover null-tolerance, lifetime, and safety
requirements.

53 tests pass. Clippy clean.
2026-04-16 04:20:55 -07:00
..
core feat(ffi): ABI surface completion — AdSnapshotSurface, focused_only, release_handle (Unit 6) 2026-04-16 04:03:34 -07:00
ffi feat(ffi): opaque list handles + image buffer length encapsulation (Unit 5) 2026-04-16 04:20:55 -07:00
linux feat: Phase 1 foundation — workspace scaffold, core engine, macOS adapter, 31 commands 2026-02-19 10:44:38 -08:00
macos feat(ffi): ABI surface completion — AdSnapshotSurface, focused_only, release_handle (Unit 6) 2026-04-16 04:03:34 -07:00
windows feat: Phase 1 foundation — workspace scaffold, core engine, macOS adapter, 31 commands 2026-02-19 10:44:38 -08:00