diff --git a/build.zig b/build.zig index b974c20..cc283bc 100644 --- a/build.zig +++ b/build.zig @@ -8,6 +8,8 @@ pub fn build(b: *std.build.Builder) void { const drv = b.option(DriverTarget, "driver", "display and input drivers combo; default: sdl2") orelse .sdl2; const disp_horiz = b.option(u32, "horiz", "display horizontal pixels count; default: 800") orelse 800; const disp_vert = b.option(u32, "vert", "display vertical pixels count; default: 480") orelse 480; + const bopts = b.addOptions(); + bopts.addOption(DriverTarget, "driver", drv); // gui build const ngui = b.addExecutable("ngui", "src/ngui.zig"); @@ -16,6 +18,7 @@ pub fn build(b: *std.build.Builder) void { ngui.pie = true; ngui.strip = strip; + ngui.addPackage(bopts.getPackage("build_options")); ngui.addIncludePath("lib"); ngui.addIncludePath("src/ui/c"); ngui.linkLibC(); @@ -80,6 +83,7 @@ pub fn build(b: *std.build.Builder) void { nd.pie = true; nd.strip = strip; + nd.addPackage(bopts.getPackage("build_options")); nifbuild.addPkg(b, nd, "lib/nif"); const niflib = nifbuild.library(b, "lib/nif"); niflib.setTarget(target); diff --git a/src/ngui.zig b/src/ngui.zig index 7e90a96..90e9fc9 100644 --- a/src/ngui.zig +++ b/src/ngui.zig @@ -5,6 +5,8 @@ const Thread = std.Thread; const comm = @import("comm.zig"); const types = @import("types.zig"); +const ui = @import("ui/ui.zig"); +const lvgl = @import("ui/lvgl.zig"); const symbol = @import("ui/symbol.zig"); /// SIGPIPE is triggered when a process attempts to write to a broken pipe. @@ -16,17 +18,7 @@ pub const keep_sigpipe = true; const stdin = std.io.getStdIn().reader(); const stdout = std.io.getStdOut().writer(); const logger = std.log.scoped(.ngui); -const lvgl_logger = std.log.scoped(.lvgl); // logs LV_LOG_xxx messages -extern "c" fn lv_timer_handler() u32; -extern "c" fn lv_log_register_print_cb(*const fn (msg: [*:0]const u8) callconv(.C) void) void; -const LvTimer = opaque {}; -const LvTimerCallback = *const fn (timer: *LvTimer) callconv(.C) void; -extern "c" fn lv_timer_create(callback: LvTimerCallback, period_ms: u32, userdata: ?*anyopaque) *LvTimer; -extern "c" fn lv_timer_del(timer: *LvTimer) void; -extern "c" fn lv_timer_set_repeat_count(timer: *LvTimer, n: i32) void; - -extern "c" fn ui_init() c_int; extern "c" fn ui_update_network_status(text: [*:0]const u8, wifi_list: ?[*:0]const u8) void; /// global heap allocator used throughout the gui program. @@ -54,11 +46,6 @@ export fn nm_get_curr_tick() u32 { return @truncate(u32, ms); } -export fn nm_lvgl_log(msg: [*:0]const u8) void { - const s = mem.span(msg); - lvgl_logger.debug("{s}", .{mem.trimRight(u8, s, "\n")}); -} - /// initiate system shutdown. export fn nm_sys_shutdown() void { logger.info("initiating system shutdown", .{}); @@ -73,8 +60,8 @@ export fn nm_tab_settings_active() void { comm.write(gpa, stdout, msg) catch |err| logger.err("nm_tab_settings_active: {any}", .{err}); } -export fn nm_request_network_status(t: *LvTimer) void { - lv_timer_del(t); +export fn nm_request_network_status(t: *lvgl.LvTimer) void { + lvgl.lv_timer_del(t); const msg: comm.Message = .{ .get_network_report = .{ .scan = false } }; comm.write(gpa, stdout, msg) catch |err| logger.err("nm_request_network_status: {any}", .{err}); } @@ -127,8 +114,11 @@ fn updateNetworkStatus(report: comm.Message.NetworkReport) !void { // can happen with a fresh connection while dhcp is still in progress. if (report.wifi_ssid != null and report.ipaddrs.len == 0) { // TODO: sometimes this is too fast, not all ip addrs are avail (ipv4 vs ipv6) - var t = lv_timer_create(nm_request_network_status, 1000, null); - lv_timer_set_repeat_count(t, 1); + if (lvgl.lv_timer_create(nm_request_network_status, 1000, null)) |t| { + lvgl.lv_timer_set_repeat_count(t, 1); + } else { + logger.err("lv_timer_create network status failed: OOM?", .{}); + } } } @@ -179,21 +169,21 @@ pub fn main() anyerror!void { }; gpa = gpa_state.allocator(); - // info level log messages are by default printed only in Debug and ReleaseSafe build modes. - lv_log_register_print_cb(nm_lvgl_log); - - const c_res = ui_init(); - if (c_res != 0) { - logger.err("ui_init failed with code {}", .{c_res}); + // initalizes display, input driver and finally creates the user interface. + ui.init() catch |err| { + logger.err("ui.init: {any}", .{err}); std.process.exit(1); - } + }; + + // start comms with daemon in a seaparate thread. const th = try Thread.spawn(.{}, commThread, .{}); th.detach(); + // main UI thread; must never block unless in idle/sleep mode // TODO: handle sigterm while (true) { ui_mutex.lock(); - var till_next_ms = lv_timer_handler(); + var till_next_ms = lvgl.lv_timer_handler(); const do_quit = quit; ui_mutex.unlock(); if (do_quit) { diff --git a/src/ui/c/drv_fbev.c b/src/ui/c/drv_fbev.c index b62c72e..b83ffd4 100644 --- a/src/ui/c/drv_fbev.c +++ b/src/ui/c/drv_fbev.c @@ -8,7 +8,8 @@ #define DISP_BUF_SIZE (NM_DISP_HOR * NM_DISP_VER / 10) -lv_disp_t *drv_init(void) +/* returns NULL on error */ +lv_disp_t *nm_disp_init(void) { fbdev_init(); @@ -28,29 +29,30 @@ lv_disp_t *drv_init(void) disp_drv.ver_res = NM_DISP_VER; disp_drv.antialiasing = 1; disp_drv.flush_cb = fbdev_flush; - lv_disp_t *disp = lv_disp_drv_register(&disp_drv); - if (disp == NULL) { - return NULL; - } + return lv_disp_drv_register(&disp_drv); +} + +int nm_indev_init(void) +{ + /* lv driver correctly closes and opens evdev again if already inited */ + evdev_init(); /* keypad input devices default group; * future-proof: don't have any atm */ lv_group_t *g = lv_group_create(); if (g == NULL) { - return NULL; + return -1; } lv_group_set_default(g); - evdev_init(); static lv_indev_drv_t touchpad_drv; lv_indev_drv_init(&touchpad_drv); touchpad_drv.type = LV_INDEV_TYPE_POINTER; touchpad_drv.read_cb = evdev_read; lv_indev_t *touchpad = lv_indev_drv_register(&touchpad_drv); if (touchpad == NULL) { - /* TODO: or continue without the touchpad? */ - return NULL; + return -1; } - return disp; + return 0; } diff --git a/src/ui/c/drv_sdl2.c b/src/ui/c/drv_sdl2.c index 0f36480..d66f44e 100644 --- a/src/ui/c/drv_sdl2.c +++ b/src/ui/c/drv_sdl2.c @@ -9,7 +9,7 @@ #define SDL_MAIN_HANDLED /* suppress "undefined reference to WinMain" */ #include SDL_INCLUDE_PATH -lv_disp_t *drv_init(void) +lv_disp_t *nm_disp_init(void) { sdl_init(); SDL_DisplayMode dm; @@ -39,11 +39,11 @@ lv_disp_t *drv_init(void) disp_drv.hor_res = NM_DISP_HOR; disp_drv.ver_res = NM_DISP_VER; disp_drv.antialiasing = 1; - lv_disp_t *disp = lv_disp_drv_register(&disp_drv); - if (disp == NULL) { - return NULL; - } + return lv_disp_drv_register(&disp_drv); +} +int nm_indev_init(void) +{ static lv_indev_drv_t mouse_drv; lv_indev_drv_init(&mouse_drv); mouse_drv.type = LV_INDEV_TYPE_POINTER; @@ -51,15 +51,16 @@ lv_disp_t *drv_init(void) lv_indev_t *mouse = lv_indev_drv_register(&mouse_drv); if (mouse == NULL) { LV_LOG_WARN("lv_indev_drv_register(&mouse_drv) returned NULL"); + return -1; } /* keypad input devices default group */ lv_group_t *g = lv_group_create(); if (g == NULL) { LV_LOG_WARN("lv_group_create returned NULL; won't set default group"); - } else { - lv_group_set_default(g); + return -1; } + lv_group_set_default(g); static lv_indev_drv_t keyboard_drv; lv_indev_drv_init(&keyboard_drv); @@ -68,9 +69,9 @@ lv_disp_t *drv_init(void) lv_indev_t *kb = lv_indev_drv_register(&keyboard_drv); if (kb == NULL) { LV_LOG_WARN("lv_indev_drv_register(&keyboard_drv) returned NULL"); - } else if (g) { - lv_indev_set_group(kb, g); + return -1; } + lv_indev_set_group(kb, g); - return disp; + return 0; } diff --git a/src/ui/c/ui.c b/src/ui/c/ui.c index 63f284d..9de7981 100644 --- a/src/ui/c/ui.c +++ b/src/ui/c/ui.c @@ -7,8 +7,6 @@ #include #include -lv_disp_t *drv_init(void); - static lv_style_t style_title; static lv_style_t style_text_muted; static lv_style_t style_btn_red; @@ -260,13 +258,8 @@ static void tab_changed_event_cb(lv_event_t *e) } } -extern int ui_init() +extern int nm_ui_init(lv_disp_t *disp) { - lv_init(); - lv_disp_t *disp = drv_init(); - if (disp == NULL) { - return -1; - } /* default theme is static */ lv_theme_t *theme = lv_theme_default_init(disp, /**/ lv_palette_main(LV_PALETTE_BLUE), /* primary */ diff --git a/src/ui/drv.zig b/src/ui/drv.zig new file mode 100644 index 0000000..ef4f342 --- /dev/null +++ b/src/ui/drv.zig @@ -0,0 +1,28 @@ +///! input and display drivers support in zig. +const std = @import("std"); +const buildopts = @import("build_options"); + +const lvgl = @import("lvgl.zig"); + +const logger = std.log.scoped(.drv); + +extern "c" fn nm_disp_init() ?*lvgl.LvDisp; +extern "c" fn nm_indev_init() c_int; + +/// initalize a display and make it active for the UI to use. +/// requires lvgl.init() to have been already called. +// TODO: rewrite lv_drivers/display/fbdev.c to make fbdev_init return an error +pub fn initDisplay() !*lvgl.LvDisp { + if (nm_disp_init()) |disp| { + return disp; + } + return error.DisplayInitFailed; +} + +/// initialize input devices: touch screen and possibly keyboard if using SDL. +pub fn initInput() !void { + const res = nm_indev_init(); + if (res != 0) { + return error.InputInitFailed; + } +} diff --git a/src/ui/lvgl.zig b/src/ui/lvgl.zig new file mode 100644 index 0000000..983a70c --- /dev/null +++ b/src/ui/lvgl.zig @@ -0,0 +1,59 @@ +///! LVGL types in zig +const std = @import("std"); + +/// initalize LVGL internals. +/// must be called before any other UI functions. +pub fn init() void { + init_once.call(); +} + +var init_once = std.once(lvglInit); + +fn lvglInit() void { + lv_log_register_print_cb(nm_lvgl_log); + lv_init(); +} + +// logs LV_LOG_xxx messages from LVGL lib +const lvgl_logger = std.log.scoped(.lvgl); + +export fn nm_lvgl_log(msg: [*:0]const u8) void { + const s = std.mem.span(msg); + // info level log messages are by default printed only in Debug and ReleaseSafe build modes. + lvgl_logger.debug("{s}", .{std.mem.trimRight(u8, s, "\n")}); +} + +extern "c" fn lv_init() void; +extern "c" fn lv_log_register_print_cb(*const fn (msg: [*:0]const u8) callconv(.C) void) void; + +/// represents lv_timer_t in C. +pub const LvTimer = opaque {}; +pub const LvTimerCallback = *const fn (timer: *LvTimer) callconv(.C) void; +/// the timer handler is the LVGL busy-wait loop iteration. +pub extern "c" fn lv_timer_handler() u32; +pub extern "c" fn lv_timer_create(callback: LvTimerCallback, period_ms: u32, userdata: ?*anyopaque) ?*LvTimer; +pub extern "c" fn lv_timer_del(timer: *LvTimer) void; +pub extern "c" fn lv_timer_set_repeat_count(timer: *LvTimer, n: i32) void; + +/// represents lv_obj_t type in C. +pub const LvObj = opaque {}; +/// delete and deallocate an object and all its children from UI tree. +pub extern "c" fn lv_obj_del(obj: *LvObj) void; + +/// represents lv_indev_t in C, an input device such as touchscreen or a keyboard. +pub const LvIndev = opaque {}; +/// deallocate and delete an input device from LVGL registry. +pub extern "c" fn lv_indev_delete(indev: *LvIndev) void; +/// return next device in the list or head if indev is null. +pub extern "c" fn lv_indev_get_next(indev: ?*LvIndev) ?*LvIndev; + +/// represents lv_disp_t in C. +pub const LvDisp = opaque {}; +/// return elapsed time since last user activity on a specific display +/// or any if disp is null. +pub extern "c" fn lv_disp_get_inactive_time(disp: ?*LvDisp) u32; +/// make it so as if a user activity happened. +/// this resets an internal counter in lv_disp_get_inactive_time. +pub extern "c" fn lv_disp_trig_activity(disp: ?*LvDisp) void; +/// force redraw dirty areas. +pub extern "c" fn lv_refr_now(disp: ?*LvDisp) void; diff --git a/src/ui/ui.zig b/src/ui/ui.zig new file mode 100644 index 0000000..343610b --- /dev/null +++ b/src/ui/ui.zig @@ -0,0 +1,22 @@ +const std = @import("std"); + +const lvgl = @import("lvgl.zig"); +const drv = @import("drv.zig"); + +const logger = std.log.scoped(.ui); + +extern "c" fn nm_ui_init(disp: *lvgl.LvDisp) c_int; + +pub fn init() !void { + lvgl.init(); + const disp = try drv.initDisplay(); + drv.initInput() catch |err| { + // TODO: or continue without the touchpad? + // at the very least must disable screen blanking timeout in case of a failure. + // otherwise, impossible to wake up the screen. */ + return err; + }; + if (nm_ui_init(disp) != 0) { + return error.UiInitFailure; + } +}