Lean runs main on a helper thread, which Cocoa rejects for video init;
export LEAN_MAIN_USE_THREAD=0 from the justfile on macOS. Wait for the
camera permission event before reading the camera format, since SDL
grants access asynchronously.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ld64.lld rejects --allow-shlib-undefined and -rpath=$ORIGIN. Add a
macOS branch with -undefined dynamic_lookup and an @executable_path
rpath, and skip the Linux-only patchelf step in the justfile.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The opaque Pixels type wrapped a void* pointing into an SDL_Surface's
pixel buffer, but nothing tied the Pixels lifetime to the surface. If
the GC collected the surface first, the Pixels pointer would dangle,
causing use-after-free.
Replace with two safe alternatives:
- updateTexture now takes a ByteArray (Lean-owned, GC-managed buffer)
plus pitch, matching how Haskell's sdl2 uses ByteString and Rust's
rust-sdl2 uses &[u8]. This is faithful to SDL_UpdateTexture's general
void* interface but requires copying pixels into a ByteArray first.
- updateTextureFromSurface is a new zero-copy convenience that takes an
SDLSurface directly. The C side extracts both pixels and pitch from
the surface internally. Because the surface is borrowed (@&) for the
call's duration, the GC cannot collect it mid-use. This is not
strictly faithful to SDL_UpdateTexture's C API, but is the common
case and avoids an unnecessary memcpy.
Also removes: Pixels type, Pixels.nonemptyType, sdl_pixels_external_class,
sdl_Surface_get_pixels, and the pixels_external_class registration in
sdl_init.
WebcamApp.lean is updated to use updateTextureFromSurface.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The old packing was:
X << 32 | Y << 16 | buttons
where X and Y were full 32-bit values. Y's upper 16 bits (bits 32-47)
overlapped with X (bits 32-63), and buttons (bits 0-31) overlapped with
Y (bits 16-47). This corrupted X whenever Y >= 65536, and corrupted Y
whenever buttons had any bits set above bit 15.
The Lean unpacking side already assumed a non-overlapping layout:
X = bits 63:32, Y = bits 31:16 (masked to 16), buttons = bits 15:0
Now the C side explicitly masks Y and buttons to 16 bits before packing,
matching the Lean expectations. This means Y is limited to [-32768,
32767] which is adequate for screen coordinates and typical relative
mode deltas.
Also fix the SDLIO error type in sdl_set_relative_mouse_mode (same
lean_mk_io_user_error issue fixed in the earlier commit).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
sdl_camera_finalizer was a no-op, so SDL cameras opened with
SDL_OpenCamera were never closed. Now calls SDL_CloseCamera.
sdl_camera_spec_finalizer was a no-op, so the SDL_CameraSpec structs
allocated with malloc in sdl_get_camera_format were never freed. Now
calls free().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a Lean extern parameter lacks @&, Lean increments the refcount
before the call and expects the C callee to lean_dec it. If the C side
declares b_lean_obj_arg (borrowed) and never decrements, the refcount
stays elevated and the object is never freed.
Add @& (borrow annotation) to parameters where C only reads the value:
- createWindow/createWindowAndRenderer: title String was owned but C
only calls lean_string_cstr (borrowed read). Leaked the string.
- renderTextureRect: sourceRect and destRect SDLFRect were owned but C
only reads scalar fields via lean_ctor_get_float. These are typically
constructed fresh each frame, so this leaked two rect objects per
frame (~48 bytes each at 60fps = ~5.6 KB/s).
- setRelativeMouseMode: window SDLWindow was owned but C only reads the
external data pointer. Leaked the window's refcount.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SDLIO is defined as EIO SDLError, where SDLError is a single-field
structure wrapping String. At runtime, SDLError is represented as just
a String (newtype erasure). Therefore, error returns from C functions
with SDLIO return types should use lean_mk_string(...) directly.
Several functions incorrectly wrapped the error string in
lean_mk_io_user_error(), which creates an IO.Error constructor. When
Lean tried to pattern-match this as SDLError (a String), it would read
the wrong memory layout, causing potential crashes on any error path.
Affected functions: sdl_create_window, sdl_create_window_and_renderer,
sdl_set_render_draw_color, sdl_set_render_draw_color_float,
sdl_render_clear, sdl_render_fill_rect, sdl_create_texture_from_surface
(one of two error paths), sdl_set_track_audio, sdl_play_track.
sdl_render_present is left unchanged because it returns IO Unit (not
SDLIO), so lean_mk_io_user_error is correct there.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two related issues with Lean's Float type being 8 bytes (double), not 4:
1. sdl_render_texture_rect read SDLFRect scalar fields at byte offsets
0,4,8,12 but lean_ctor_get_float reads 8-byte doubles, so the correct
offsets are 0,8,16,24. The old offsets caused each read to straddle
two adjacent fields, producing garbage values for y/w/h.
2. sdl_set_render_draw_color_float declared its parameters as float (4
bytes) but Lean passes Float as double (8 bytes) at the ABI level.
On x86-64, Lean puts doubles in XMM registers and the C function
interpreted the wrong bits—e.g. double 1.0 (0x3FF0000000000000) has
lower 32 bits 0x00000000, so all colors read as 0.
Both functions now use double to match Lean's Float ABI, with explicit
(float) casts where SDL3 APIs expect float.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
sdlObj was only used by libleansdl, so inline the C compilation
directly into that target to reduce indirection.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Also fixes inconsistent indentation on webcam-app and renames libList/moreLinkArgs
to avoid shadowing Lake field names.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>