Files
MeowOS-SDK/tuku/ui/lock/lock.c
T
AskaEth 87f4ad0a82 图库: 锁屏长按退格在真机可达(仅在锁屏页启用长按)
M29 评审 P2-1:SDK 长按是 opt-in,全仓没有运行时调用
meow_ui_set_long_press_cb,meow_input_long_press_poll 恒返回 0,真机上
MEOW_EV_LONG_PRESS 永不产生——「长按退格清空」只表现为删一位。

- app.c 注册 no-op 长按回调,但只在 ACT_LOCK 为顶层页时启用(进主循环前对齐
  一次 + on_tick 每拍对齐),解锁 pop 后置 NULL。全程开启会让长按触发后的
  抬手不再产生 TAP/SWIPE,吞掉书库/阅读器里「按住 >=500ms」的点击。
- test_settings_lock_host:长按用例改走真实 SDK 路径——未注册 poll==0(P2
  根因)、注册后按住 700ms 产出 LONG_PRESS 且坐标=退格键心、被锁屏 view 消费、
  抬手不补发 TAP、解锁后关掉再 poll==0。
- lock.c 头注释补上该 opt-in 依赖,避免以后只看锁屏页时误判可达。
2026-11-06 11:46:31 +08:00

326 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 回书库;
* - 长按「退格」清空已输入(不做惩罚性计数/锁定);SDK 长按是 opt-in,需 app.c 在锁屏
* 为顶层页时注册 meow_ui_set_long_press_cb(事件仍先走本页 view 分发),否则真机上
* MEOW_EV_LONG_PRESS 不会产生、长按只会退化成单删一位;
* - 左缘返回**必须**由本页 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);
}