Files
MeowOS-SDK/tuku/ui/lock/lock.c
T
AskaEth 606a04a44e 图库: 锁屏 PIN 改页内自绘 12 键数字键盘(不再弹系统 IME)
真机 goodix 抬手长尾会让阻塞式 MEOW_IME_NUM 打开即被「点背景=取消」
关掉,表现为「PIN 按钮点好几下才弹键盘」。锁屏改自绘 3×4 键盘
(1-9 / 退格·0·确认,键 137×96、间距 10px、底边留 28px 上滑热区),
完全离线、无 IME 会话,首次点按必响应。

- 4-8 位(MR_PIN_MIN_LEN/MAX_LEN),顶部星号显示已输入位数,不回显明文;
  满 4 位后「确认」才可用;错误→提示+清空;通过→page_pop 回书库
- 长按退格清空;退格空缓冲为 no-op;8 位上限截断
- 颜色全部取 meow_theme_get();按键走 meow_btn_create_variant(TONAL/
  OUTLINED/FILLED),沿用 SDK 按压高亮/禁用变淡
- 锁屏仍消费全部 SWIPE/TAP/LONG_PRESS(左缘绕过 PIN 的路径不变),
  物理键返回 0 交 app.c on_key 仅日志
- 与设置页共用 logic/pin 的 mr_pin_verify/mr_pin_set,不复制校验逻辑
- 日志:lock: pad ready / pin wrong / pin ok(真机验收判据)

test_settings_lock_host.c:锁屏用例从 IME 注入改为按键点击(12 键归属、
键高/间距/热区几何、空确认/不足 4 位、错 PIN 清空重输、8 位上限+退格、
空退格、长按清空、不打开系统 IME);设置页仍走真实 IME 注入路径。
2026-11-06 11:46:31 +08:00

324 lines
12 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 建 12 键数字键盘(1-9 / 退格·0·确认,owner=ACT_LOCK)。**不再调系统
* 输入法**:真机 goodix 抬手长尾会让 MEOW_IME_NUM 打开即被「点背景=取消」关掉,表现为
* 「点好几下才弹键盘」;页内键盘完全离线、无 IME 会话,首次点按必响应;
* - 4-8 位数字(logic/pin 的 MR_PIN_MIN_LEN/MAX_LEN),顶部只显示星号/圆点数位,不回显明文;
* 满 4 位后「确认」才可用;错误 → 清空 + 提示;通过 → page_pop 回书库;
* - 长按「退格」清空已输入(不做惩罚性计数/锁定);
* - 左缘返回**必须**由本页 on_event 消费 SWIPE(meow_view_dispatch_event 先于 app.c 的
* on_swipe;否则 app.c 对非 library 页无条件 page_pop,PIN 会被绕过);TAP/LONG_PRESS
* 也一律消费,物理键不接(返回 0,交 app.c 的 on_key 仅日志);屏底 28px 上滑热区
* 不被键盘覆盖(键盘底边留 28+12px);
* - PIN 未启用时 app.c 直接 push 书库,本页不会被进入。
*
* 安全口径(诚实标注):PIN 防的是随手翻看,不是安全边界——TF 卡可拔出直读,配置目录
* 可改。存储为加盐迭代哈希(logic/pin.c),不引入加密库;锁屏文案也如实告知用户。
*
* 控件归属(§6.1,M3 桩注释①):on_create 时 activity 栈为空,控件一律在 on_enter 建。
* 键位顺序(宿主测试依赖):1..9、退格、0、确认 = 下标 0..11。
*/
#include "tuku_internal.h"
#include "logic/pin.h"
#include <stdio.h>
#include <string.h>
#define LK_KEY_COUNT 12
#define LK_GRID_COLS 3
#define LK_GRID_ROWS 4
#define LK_GAP 10 /* 键间距(≥8;触摸目标互不粘连) */
#define LK_KEY_MIN_H 64 /* 最小键高 */
#define LK_KEY_MAX_H 96
#define LK_KEY_MAX_W 160
#define LK_SIDE 24 /* 键盘左右留白 */
#define LK_BOTTOM_HOT 28 /* 屏底上滑回桌面热区:键盘不得覆盖 */
#define LK_BOTTOM_PAD 12 /* 再留一点呼吸,避免贴住热区 */
#define LK_HEAD_MIN 220 /* 键盘上方留给标题/圆点/提示的最小高度 */
/* 键下标 = 创建序(宿主测试依赖此序)。 */
enum { LK_K1 = 0, LK_K9 = 8, LK_KBACK = 9, LK_K0 = 10, LK_KOK = 11 };
static mr_page_ctrl g_ctrl;
static meow_widget *g_keys[LK_KEY_COUNT];
static char g_pin[MR_PIN_MAX_LEN + 1];
static int g_pin_len;
static char g_msg[64];
static int g_msg_err;
/* meow_view on_event 的事件码:公共伞头未导出(sdk/meow_ui_internal.h:
* MEOW_EV_TAP=2 / MEOW_EV_SWIPE=3 / MEOW_EV_LONG_PRESS=8,值稳定),按契约镜像。 */
#define MR_VEV_TAP 2
#define MR_VEV_SWIPE 3
#define MR_VEV_LONG_PRESS 8
int lock_view_should_show(void)
{
return mr_pin_enabled();
}
/* ============================================================ */
/* 几何(480×720:键 ~137×96,四行 + 屏底热区留白) */
/* ============================================================ */
static void lock_keypad_geom(int *x0, int *y0, int *kw, int *kh)
{
int w = meow_ui_width();
int h = meow_ui_height();
int sb = meow_ui_statusbar_height();
int bottom = h - LK_BOTTOM_HOT - LK_BOTTOM_PAD;
int top_min = sb + LK_HEAD_MIN;
int kw_ = (w - 2 * LK_SIDE - (LK_GRID_COLS - 1) * LK_GAP) / LK_GRID_COLS;
if (kw_ > LK_KEY_MAX_W) kw_ = LK_KEY_MAX_W;
if (kw_ < LK_KEY_MIN_H) kw_ = LK_KEY_MIN_H;
int kh_ = (bottom - top_min - (LK_GRID_ROWS - 1) * LK_GAP) / LK_GRID_ROWS;
if (kh_ > LK_KEY_MAX_H) kh_ = LK_KEY_MAX_H;
if (kh_ < LK_KEY_MIN_H) kh_ = LK_KEY_MIN_H;
*kw = kw_;
*kh = kh_;
*x0 = (w - (LK_GRID_COLS * kw_ + (LK_GRID_COLS - 1) * LK_GAP)) / 2;
*y0 = bottom - (LK_GRID_ROWS * kh_ + (LK_GRID_ROWS - 1) * LK_GAP);
if (*y0 < top_min) *y0 = top_min; /* 极小屏兜底;480×720 不会触发 */
}
static void lock_key_rect(int idx, int *x, int *y, int *w, int *h)
{
int x0, y0, kw, kh;
lock_keypad_geom(&x0, &y0, &kw, &kh);
int row = idx / LK_GRID_COLS, col = idx % LK_GRID_COLS;
*x = x0 + col * (kw + LK_GAP);
*y = y0 + row * (kh + LK_GAP);
*w = kw;
*h = kh;
}
/* ============================================================ */
/* PIN 状态 */
/* ============================================================ */
static void lock_refresh(void)
{
meow_ui_invalidate(0, 0, meow_ui_width(), meow_ui_height());
}
/* 「确认」仅在 ≥4 位时可用;其余情况保持禁用(SDK 按状态机变淡且不响应命中)。 */
static void lock_sync_confirm(void)
{
if (g_keys[LK_KOK])
meow_widget_set_enabled(g_keys[LK_KOK], g_pin_len >= MR_PIN_MIN_LEN);
}
static void lock_clear_digits(void)
{
g_pin_len = 0;
g_pin[0] = '\0';
lock_sync_confirm();
}
static void lock_append_digit(int d)
{
if (g_pin_len >= MR_PIN_MAX_LEN) return; /* 8 位上限:多余按键忽略 */
g_pin[g_pin_len++] = (char)('0' + d);
g_pin[g_pin_len] = '\0';
g_msg[0] = '\0';
g_msg_err = 0;
lock_sync_confirm();
lock_refresh();
}
static void lock_backspace(void)
{
if (g_pin_len > 0) g_pin[--g_pin_len] = '\0';
g_msg[0] = '\0';
g_msg_err = 0;
lock_sync_confirm();
lock_refresh();
}
static void lock_clear_all(void)
{
lock_clear_digits();
g_msg[0] = '\0';
g_msg_err = 0;
lock_refresh();
}
static void lock_confirm(void)
{
if (g_pin_len < MR_PIN_MIN_LEN) {
snprintf(g_msg, sizeof g_msg, "请输入至少 %d 位数字", MR_PIN_MIN_LEN);
g_msg_err = 1;
lock_refresh();
return;
}
if (!mr_pin_verify(g_pin)) { /* 与设置页同一校验入口(logic/pin) */
meow_log(MEOW_LOG_INFO, "lock: pin wrong (len=%d), buffer cleared", g_pin_len);
snprintf(g_msg, sizeof g_msg, "PIN 错误,请重试");
g_msg_err = 1;
lock_clear_digits();
lock_refresh();
return;
}
g_msg[0] = '\0';
g_msg_err = 0;
meow_log(MEOW_LOG_INFO, "lock: pin ok");
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 x0, y0, kw, kh;
lock_keypad_geom(&x0, &y0, &kw, &kh);
(void)x0; (void)kw; (void)kh;
/* 自绘整页底色:验证前不露任何书库内容(本页是顶层唯一 entered 页)。 */
meow_ui_fill_rect(0, sb, w, h - sb, th->bg);
int title_y = sb + 36;
meow_ui_text_center(w / 2, title_y, "图库已锁定", th->font_title, th->onSurface);
/* 已输入位数:星号=已输入,点=空位;不回显明文。 */
char dots[MR_PIN_MAX_LEN * 2 + 1];
int n = 0;
for (int i = 0; i < MR_PIN_MAX_LEN; i++) {
dots[n++] = (i < g_pin_len) ? '*' : '.';
if (i + 1 < MR_PIN_MAX_LEN) dots[n++] = ' ';
}
dots[n] = '\0';
int dots_y = title_y + meow_ui_text_height(th->font_title) + 18;
meow_ui_text_center(w / 2, dots_y, dots, th->font_title,
g_pin_len > 0 ? th->primary : th->onSurfaceVariant);
int status_y = dots_y + meow_ui_text_height(th->font_title) + 8;
if (g_msg[0])
meow_ui_text_center(w / 2, status_y, g_msg, th->font_body,
g_msg_err ? th->error : th->primary);
else
meow_ui_text_center(w / 2, status_y, "输入 4-8 位数字后点确认", th->font_body,
th->onSurfaceVariant);
/* 安全口径(如实告知),紧贴键盘上方。 */
int note_y = y0 - 54;
meow_ui_text_center(w / 2, note_y, "PIN 仅用于防止随手查看,", th->font_body,
th->onSurfaceVariant);
meow_ui_text_center(w / 2, note_y + 24, "TF 卡可被直接读取,不构成安全边界。", th->font_body,
th->onSurfaceVariant);
}
static void lock_place(meow_view *v)
{
int w = meow_ui_width();
int h = meow_ui_height();
v->x = 0; v->y = 0; v->w = w; v->h = h;
for (int i = 0; i < LK_KEY_COUNT; i++) {
int x, y, kw, kh;
lock_key_rect(i, &x, &y, &kw, &kh);
if (g_keys[i]) meow_widget_set_rect(g_keys[i], x, y, kw, kh);
}
}
/* ============================================================ */
/* 事件 */
/* ============================================================ */
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_LONG_PRESS) {
/* 长按「退格」= 清空(可选增强;其余长按也只吞掉,不透传其它页)。 */
if (meow_widget_hit(g_keys[LK_KBACK], a, b)) lock_clear_all();
return 1;
}
if (ev == MR_VEV_TAP) {
for (int i = 0; i < LK_KEY_COUNT; i++) {
if (!g_keys[i] || !meow_widget_hit(g_keys[i], a, b)) continue;
if (i <= LK_K9) lock_append_digit(i + 1); /* 1..9 */
else if (i == LK_KBACK) lock_backspace();
else if (i == LK_K0) lock_append_digit(0);
else lock_confirm();
return 1;
}
return 1; /* 锁屏页消费所有点击(含空白区),不向 App 其它页透传 */
}
return 0; /* 物理键交 app.c 的 on_key 仅日志(不映射数字/确认) */
}
/* ============================================================ */
/* 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_pin_len = 0;
g_pin[0] = '\0';
g_msg[0] = '\0';
g_msg_err = 0;
static const char *const labels[LK_KEY_COUNT] = {
"1", "2", "3", "4", "5", "6", "7", "8", "9", "退格", "0", "确认",
};
for (int i = 0; i < LK_KEY_COUNT; i++) {
if (g_keys[i]) continue;
/* page_push 已先 meow_ui_activity_push(ACT_LOCK) → 控件 owner 正确。 */
int variant = (i == LK_KOK) ? MEOW_BTN_FILLED
: (i == LK_KBACK) ? MEOW_BTN_OUTLINED
: MEOW_BTN_TONAL;
g_keys[i] = meow_btn_create_variant(0, 0, 96, LK_KEY_MIN_H, labels[i], variant);
if (g_keys[i]) meow_view_add_widget(v, g_keys[i]);
}
lock_place(v);
lock_sync_confirm();
meow_log(MEOW_LOG_INFO, "lock: pad ready (12 keys, screen %dx%d)",
meow_ui_width(), meow_ui_height());
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;
for (int i = 0; i < LK_KEY_COUNT; i++) g_keys[i] = 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);
}