Skip to content

Commit 488d933

Browse files
committed
ui: harden modal widget user_data handling
Replace function-pointer-in-userdata pattern with a data context struct.
1 parent 7e98423 commit 488d933

File tree

1 file changed

+42
-14
lines changed

1 file changed

+42
-14
lines changed

src/ui/widget.zig

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,28 @@ pub fn topdrop(onoff: enum { show, remove }) void {
6060
/// provided as btns arg to modal.
6161
pub const ModalButtonCallbackFn = *const fn (index: usize) void;
6262

63+
/// Context stored in the modal window's user data.
64+
/// This avoids storing a function pointer directly in LVGL user_data (void*),
65+
/// which is not portable/safe and can break across Zig versions/ABIs.
66+
const ModalCtx = struct {
67+
cb: ModalButtonCallbackFn,
68+
};
69+
6370
/// shows a non-dismissible window using the whole screen real estate;
6471
/// for use in place of lv_msgbox_create.
6572
///
6673
/// while all heap-alloc'ed resources are free'd automatically right before cb is called,
6774
/// the value of title, text and btns args must live at least as long as cb; they are
6875
/// memory-managed by the callers.
69-
///
70-
/// note: the cb callback must have @alignOf(ModalbuttonCallbackFn) alignment.
7176
pub fn modal(title: [*:0]const u8, text: [*:0]const u8, btns: []const [*:0]const u8, cb: ModalButtonCallbackFn) !void {
7277
const win = try lvgl.Window.newTop(60, title);
7378
errdefer win.destroy(); // also deletes all children created below
74-
win.setUserdata(cb);
79+
80+
// Store a data pointer in LVGL user_data (safe), containing the callback.
81+
const ctx = try std.heap.c_allocator.create(ModalCtx);
82+
errdefer std.heap.c_allocator.destroy(ctx);
83+
ctx.* = .{ .cb = cb };
84+
win.setUserdata(ctx);
7585

7686
const wincont = win.content().flex(.column, .{ .cross = .center, .track = .center });
7787
const msg = try lvgl.Label.new(wincont, text, .{ .pos = .center });
@@ -89,26 +99,44 @@ pub fn modal(title: [*:0]const u8, text: [*:0]const u8, btns: []const [*:0]const
8999
const btn = try lvgl.TextButton.new(btncont, btext);
90100
btn.setFlag(.event_bubble);
91101
btn.setFlag(.user1); // .user1 indicates actionable button in callback
92-
btn.setUserdata(@ptrFromInt(i)); // button index in callback
102+
103+
// Store (i+1) so userdata is never null (0).
104+
// In callback: btn_index = intFromPtr - 1.
105+
btn.setUserdata(@ptrFromInt(i + 1));
106+
93107
btn.setWidth(btnwidth);
94108
if (i == 0) {
95109
btn.addStyle(lvgl.nm_style_btn_red(), .{});
96110
}
97111
}
112+
113+
// Pass the window object pointer as event user data (edata)
98114
_ = btncont.on(.click, nm_modal_callback, win.lvobj);
99115
}
100116

101117
export fn nm_modal_callback(e: *lvgl.LvEvent) callconv(.C) void {
102-
if (e.userdata()) |edata| {
103-
const target = lvgl.Container{ .lvobj = e.target() }; // type doesn't really matter
104-
if (!target.hasFlag(.user1)) { // .user1 is set in modal setup
105-
return;
106-
}
118+
const edata = e.userdata() orelse return;
107119

108-
const btn_index = @intFromPtr(target.userdata());
109-
const win = lvgl.Window{ .lvobj = @ptrCast(edata) };
110-
const cb: ModalButtonCallbackFn = @alignCast(@ptrCast(win.userdata()));
111-
win.destroy();
112-
cb(btn_index);
120+
const target = lvgl.Container{ .lvobj = e.target() }; // type doesn't really matter
121+
if (!target.hasFlag(.user1)) { // .user1 is set in modal setup
122+
return;
113123
}
124+
125+
const idx_any = target.userdata() orelse return;
126+
const btn_index: usize = @intFromPtr(idx_any) - 1;
127+
128+
const win = lvgl.Window{ .lvobj = @ptrCast(edata) };
129+
130+
const ctx_any = win.userdata() orelse return;
131+
const ctx: *ModalCtx = @ptrCast(@alignCast(ctx_any));
132+
const cb = ctx.cb;
133+
134+
// Destroy LVGL objects first (matches your earlier intent: free UI resources)
135+
win.destroy();
136+
137+
// Free the context we allocated for this modal
138+
std.heap.c_allocator.destroy(ctx);
139+
140+
// Now call user callback (may open other screens, etc.)
141+
cb(btn_index);
114142
}

0 commit comments

Comments
 (0)