/* ui/lock/lock.c — 锁屏页(ACT_LOCK):APP 启动 PIN。 * * 流程(用户原话「允许用户在APP设置中设置APP启动 PIN」): * - lock_view_should_show() = logic/pin 的 mr_pin_enabled();app.c 据此在启动时先 push * 本页,验证通过前书库不进入/不绘制(view 注册表只画已进入的页,widget 还按 owner 过滤); * - 本页 on_enter 建「输入 PIN」按钮(owner=ACT_LOCK),点按弹系统数字键盘(MEOW_IME_NUM), * 校验通过 → page_pop 回书库;错误 → 就地提示重试(不做惩罚性锁定/计数); * - 左缘返回**必须**由本页 on_event 消费 SWIPE(meow_view_dispatch_event 先于 app.c 的 * on_swipe;否则 app.c 对非 library 页无条件 page_pop,PIN 会被绕过); * - PIN 未启用时 app.c 直接 push 书库,本页不会被进入。 * * 安全口径(诚实标注):PIN 防的是随手翻看,不是安全边界——TF 卡可拔出直读,配置目录 * 可改。存储为加盐迭代哈希(logic/pin.c),不引入加密库;锁屏文案也如实告知用户。 * * 控件归属(§6.1,M3 桩注释①):on_create 时 activity 栈为空,控件一律在 on_enter 建。 */ #include "tuku_internal.h" #include "logic/pin.h" #include #include static mr_page_ctrl g_ctrl; static meow_widget *g_btn; static char g_msg[64]; static int g_msg_err; static int g_waiting; /* 1=数字键盘打开中(上部提示改为输入说明) */ static char g_ime_pin[MR_PIN_MAX_LEN + 12]; /* meow_view on_event 的事件码:公共伞头未导出(sdk/meow_ui_internal.h: * MEOW_EV_TAP=2 / MEOW_EV_SWIPE=3,值稳定),此处按契约镜像。 */ #define MR_VEV_TAP 2 #define MR_VEV_SWIPE 3 int lock_view_should_show(void) { return mr_pin_enabled(); } /* ============================================================ */ /* PIN 输入(系统数字键盘) */ /* ============================================================ */ static void ime_pin_change(const char *text, void *ctx) { (void)ctx; snprintf(g_ime_pin, sizeof g_ime_pin, "%s", text ? text : ""); } /* 阻塞数字键盘;返回 1=收集到非空文本(关闭键盘即结束/校验)。 * SDK 现状:键盘「确定」只置内部 submit 标记不关闭,关闭路径(取消/点背景)又把标记清 0, * 故 meow_ui_text_input 的返回值不可作“确认”判据;文本经 on_change 实时收集,空=取消。 */ static int lock_read_pin(char *out, int outsz) { meow_ime_ctx c; memset(&c, 0, sizeof c); c.mode = MEOW_IME_NUM; c.maxlen = (int)sizeof g_ime_pin; c.on_change = ime_pin_change; c.ctx = NULL; g_ime_pin[0] = '\0'; g_msg[0] = '\0'; g_msg_err = 0; g_waiting = 1; /* 锁屏上部提示改为输入说明 */ meow_ui_invalidate(0, 0, meow_ui_width(), meow_ui_height()); /* 键盘区背景快照取当前帧 */ (void)meow_ui_text_input(&c); g_waiting = 0; snprintf(out, (size_t)outsz, "%s", g_ime_pin); return out[0] != '\0'; } static void lock_attempt(void) { char pin[32] = ""; if (!lock_read_pin(pin, (int)sizeof pin)) { meow_ui_invalidate(0, 0, meow_ui_width(), meow_ui_height()); return; /* 关闭键盘未输入:留在锁屏 */ } if (!mr_pin_verify(pin)) { snprintf(g_msg, sizeof g_msg, "PIN 错误,请重试"); g_msg_err = 1; meow_ui_invalidate(0, 0, meow_ui_width(), meow_ui_height()); return; } g_msg[0] = '\0'; g_msg_err = 0; meow_view_page_pop(); /* 通过 → 回书库(ACT_LIBRARY on_enter) */ } /* ============================================================ */ /* 布局/绘制 */ /* ============================================================ */ static void lock_draw(meow_view *v, const meow_theme *th, void *ctx) { (void)v; (void)ctx; int w = meow_ui_width(); int h = meow_ui_height(); int sb = meow_ui_statusbar_height(); int cy = sb + (h - sb) / 2; /* 自绘整页底色:验证前不露任何书库内容(本页是顶层唯一 entered 页)。 */ meow_ui_fill_rect(0, sb, w, h - sb, th->bg); meow_ui_text_center(w / 2, cy - 150, "图库已锁定", th->font_title, th->onSurface); if (g_waiting) { meow_ui_text_center(w / 2, cy - 104, "输入 4-8 位数字后关闭键盘", th->font_body, th->onSurface); meow_ui_text_center(w / 2, cy - 76, "空输入 = 取消,回到锁屏", th->font_body, th->onSurfaceVariant); } else { meow_ui_text_center(w / 2, cy - 104, "输入启动 PIN 继续", th->font_body, th->onSurfaceVariant); meow_ui_text_center(w / 2, cy - 76, "4-8 位数字 · 系统数字键盘", th->font_body, th->onSurfaceVariant); } if (g_msg[0]) { meow_ui_text_center(w / 2, cy - 26, g_msg, th->font_body, g_msg_err ? th->error : th->primary); } /* 安全口径(如实告知):PIN 不是安全边界。 */ meow_ui_text_center(w / 2, h - 86, "PIN 仅用于防止随手查看,", th->font_body, th->onSurfaceVariant); meow_ui_text_center(w / 2, h - 58, "TF 卡可被直接读取,不构成安全边界。", th->font_body, th->onSurfaceVariant); } static void lock_place(meow_view *v) { int w = meow_ui_width(); int h = meow_ui_height(); int sb = meow_ui_statusbar_height(); int cy = sb + (h - sb) / 2; v->x = 0; v->y = 0; v->w = w; v->h = h; int bw = 220, bh = 52; if (g_btn) meow_widget_set_rect(g_btn, (w - bw) / 2, cy + 20, bw, bh); } /* ============================================================ */ /* 事件 */ /* ============================================================ */ static int lock_on_event(meow_view *v, int ev, int a, int b, void *ctx) { (void)v; (void)ctx; /* 左缘返回(以及任何滑动)一律吞掉:锁屏不允许退出 App,也不允许绕过 PIN。 */ if (ev == MR_VEV_SWIPE) return 1; if (ev == MR_VEV_TAP) { int x = a, y = b; if (meow_widget_hit(g_btn, x, y)) lock_attempt(); return 1; /* 锁屏页消费点击,不向 App 其它页透传 */ } return 0; } /* ============================================================ */ /* view 生命周期 */ /* ============================================================ */ static void lock_page_enter(meow_view *v, void *ctx) { ((mr_page_ctrl *)ctx)->active = 1; meow_ui_set_fullscreen(0); meow_ui_set_page_bg(meow_theme_get()->bg); g_msg[0] = '\0'; g_msg_err = 0; if (!g_btn) { /* page_push 已先 meow_ui_activity_push(ACT_LOCK) → 控件 owner 正确。 */ g_btn = meow_btn_create_variant(0, 0, 220, 52, "输入 PIN", MEOW_BTN_FILLED); if (g_btn) meow_view_add_widget(v, g_btn); } lock_place(v); mr_nav_report(); meow_ui_paint_full(); } static void lock_page_leave(meow_view *v, void *ctx) { (void)v; ((mr_page_ctrl *)ctx)->active = 0; } static void lock_page_destroy(meow_view *v, void *ctx) { (void)v; (void)ctx; g_btn = NULL; /* 控件随 meow_view_free 释放(v->widgets) */ } meow_view *lock_view_create(void) { static const meow_view_hooks h = { .on_enter = lock_page_enter, .on_leave = lock_page_leave, .on_destroy = lock_page_destroy, .on_draw = lock_draw, .on_event = lock_on_event, }; return meow_view_create(ACT_LOCK, MEOW_VIEW_ACTIVITY, &h, &g_ctrl); }