Files
MeowOS-SDK/tuku/ui/lock/lock.c
T
AskaEth cca3906c80 图库: M5 设置页(阅读方向/PIN/主题状态)与启动 PIN 锁屏;阅读方向持久化(评审 P1)
logic/pin(纯 C):加盐迭代哈希(FNV-1a ×1000 轮 + 每次设置新随机盐,16 位 hex)经
meow_config_* 持久化 pin.enabled/pin.hash/pin.salt;提供 set/verify/change/disable 与
4-8 位纯数字校验;enabled=1 但盐/哈希残缺时 fail-closed;落盘半途失败会还原旧盐,不留
「新盐+旧哈希」的双失效状态。

ui/settings(ACT_SETTINGS):MD3 分组表单(meow_listview 容器圆角卡 + 段头标签)——
阅读方向 RTL/LTR 走 mr_reader_set_direction;启动 PIN 开关与修改(开启/修改均两遍
MEOW_IME_NUM 确认);版本与主题状态行(跟随系统 + 当前明暗,随 app.c 主题 listener
整帧重绘)。控件一律在 on_enter 建(栈顶=ACT_SETTINGS),避免 §6.1 owner 绑到
ACT_LIBRARY 的跨页命中泄漏。

ui/lock(ACT_LOCK):lock_view_should_show() 读 logic/pin;启用时启动先入锁屏,验证前
不绘制任何书库内容;「输入 PIN」按钮弹系统数字键盘(MEOW_IME_NUM),错 PIN 就地提示
重试(无惩罚性锁定),正确则 page_pop 回书库;on_event 消费 SWIPE——左缘返回不能绕过
PIN 或退出 App(meow_view_dispatch_event 先于 app.c 的 on_swipe)。

logic/reader(评审 P1 修复,最小持久化):mr_reader_set_direction 成功切换后 best-effort
写 meow_config("reader.dir","rtl"/"ltr"),写失败只打 WARN 不阻断本次切换;
mr_reader_get_direction 首次调用惰性读一次配置恢复(无配置/非法值 → 默认 RTL),之后
纯内存快路径。宿主测试经 -DMR_READER_TEST_HOOKS 的 mr_reader_test_reset() 模拟重启。

测试/构建:test_pin(85 项,PIN 全分支,含单键半途落盘失败);test_settings_reader_persist
(19 项:默认/落盘/重启恢复/坏值/非法值/只读一次/写失败只告警);可选宿主集成
test_settings_lock_host(真实 SDK view/activity/widget + IME 注入,端到端验证错/对 PIN
解锁与设置页两遍开启;SDK 无源码时 SKIP);run_tests.sh 保持 46/46 并纳入上述测试
(reader.c 的配置/日志依赖由 test/settings_reader_stub.c 存根),build_tuku.sh 加入
logic/pin.c。交叉构建静态 MIPS ELF(zig-mipsel)零警告。

安全口径:PIN 防随手翻看,不是安全边界——TF 卡可拔出直读、配置可改;存储不使用加密库,
锁屏与设置页文案均如实标注该局限。
2026-11-04 21:25:17 +08:00

210 lines
7.8 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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 <stdio.h>
#include <string.h>
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);
}