ui: move all C lvgl/zig code into lvgl.zig and drv.zig

the idea is for all zig code to use C LVGL only via this
new module lvgl.zig, all display and input driver/devices is handled
by drv.zig.

this makes C/zig nicely compartmentalized in just a handful places.
pull/20/head
alex 2 years ago
parent 5c60b92f85
commit 021c810dc7
Signed by: x1ddos
GPG Key ID: FDEFB4A63CBD8460

@ -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);

@ -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) {

@ -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;
}

@ -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;
}

@ -7,8 +7,6 @@
#include <stdlib.h>
#include <unistd.h>
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 */

@ -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;
}
}

@ -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;

@ -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;
}
}