/* lock_screen_ui.c — MeowOS 锁屏页(系统级全屏页面:launcher 渲染,App 不可见不可触)。 * * 用户定案(2026-09-02): * - 息屏唤醒后(默认开,system.pin_lock_on_wake=1 可关)+ 电源菜单「锁定」手动锁;开机不锁。 * - 数字键盘直接用 SDK 按钮画(不调 IME);输入 4 位自动校验;错 5 次锁 5 分钟(持久化)。 * - 正确 → 进主屏(return 1);锁屏页期间电源键=息屏(允许再睡,return 0),音量键不动; * 不响应左缘返回 / 通知中心手势(本函数不实现这些手势,触摸只认键盘 tap)。 * - 输入不回显数字(● 遮罩)。兜底见 lock_screen.c(SSH root 删 system.pin 即解锁)。 * * 渲染/输入循环与电源菜单同构(meow_touch + kd_* + kp_*),阻塞到解锁或电源息屏。 * 纯逻辑在 lock_screen.c(可宿主单测);本文件只做 UI 与输入胶水。 */ #include "meow_launcher_internal.h" /* §310 残影:锁屏处于 App 前台叠加会话时置位当前前台 App pid(run_app_lock_overlay 写入)。 * run_lock_screen 每圈据此探测 App 是否已死(组合 App 冻结缓冲 evdev 会在解锁瞬间回放成点击 / * 叠加期 OOM 杀让 fb0 留残帧——探到死即中止锁屏,交回 waitpid 循环回收 + run_menu 重绘主屏)。 * 菜单直接锁屏(无 App)时保持 0 = 不探测。 */ int g_lock_overlay_pid = 0; /* ---- 键盘几何(480×720):3 列×4 行圆角按钮 ---- */ #define LK_X0 32 /* 首列左缘 */ #define LK_COLW 130 /* 列宽 */ #define LK_COLG 8 /* 列间隙 */ #define LK_Y0 320 /* 首行顶缘 */ #define LK_ROW_H 82 /* 行高 */ #define LK_ROWG 10 /* 行间隙 */ #define LK_ROWS 4 #define LK_COLS 3 /* 12 键(行优先):digit=-2 清空 / -1 退格 / >=0 数字 */ #define LK_ACT_CLR (-2) #define LK_ACT_BS (-1) static const int lk_keys[LK_ROWS][LK_COLS] = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 }, { LK_ACT_CLR, 0, LK_ACT_BS }, }; /* 键盘按钮 row*3+col 的命中矩形(x,y,w,h) */ static void lk_key_rect(int row, int col, int *x, int *y, int *w, int *h) { *x = LK_X0 + col * (LK_COLW + LK_COLG); *y = LK_Y0 + row * (LK_ROW_H + LK_ROWG); *w = LK_COLW; *h = LK_ROW_H; } /* 居中文本(真实字体优先;回退 ASCII 点阵) */ static void lk_text(int cx, int y, const char *s, int size, uint32_t color) { if (mfont_ready) { int w = meow_font_width_text(s, size); meow_font_drawtext((uint16_t *)fb, cx - w / 2, y, s, rgb565(color), size); } else { draw_text(cx - (int)strlen(s) * 3, y + 1, s, 1, color); } } /* 单个 PIN 圆点:filled=1 实心(已输入),0=空心(未输入)。 */ static void lk_pin_dot(int cx, int cy, int r, int filled, uint32_t ink, uint32_t bg) { if (filled) { fill_circle(cx, cy, r, ink); } else { fill_circle(cx, cy, r, ink); /* 外圈(描边) */ fill_circle(cx, cy, r - 3, bg); /* 内孔(表面底色) */ } } static void lk_draw_pin(int entered, const meow_ui_theme_t *th) { int cx = fb_xres / 2; int y = 210; int gap = 48, r = 20; int x0 = cx - (3 * gap) / 2; /* 首圆圆心;四圆等距 gap */ for (int i = 0; i < LCK_PIN_LEN; i++) lk_pin_dot(x0 + i * gap, y, r, i < entered, th->primary, th->surface); } static void lk_draw(int entered, const char *status, time_t locked_until) { const meow_ui_theme_t *th = s_ui_theme(); fill_rect(0, 0, fb_xres, fb_yres, th->surface); /* 全屏表面底色 */ lk_text(fb_xres / 2, 120, locked_until > 0 ? "已锁定" : "输入 PIN 解锁", 24, th->on_surface); lk_draw_pin(entered, th); lk_text(fb_xres / 2, 260, status, 18, status[0] ? th->error : th->on_surface_variant); for (int r = 0; r < LK_ROWS; r++) { for (int c = 0; c < LK_COLS; c++) { int x, y, w, h; lk_key_rect(r, c, &x, &y, &w, &h); fill_round_rect(x, y, w, h, 12, th->surface_container); int key = lk_keys[r][c]; if (key >= 0) { char d[2] = { (char)('0' + key), 0 }; if (mfont_ready) { int fw = meow_font_width_text(d, 30); meow_font_drawtext((uint16_t *)fb, x + w / 2 - fw / 2, y + LK_ROW_H / 2 - meow_font_line_height(30) / 2, d, rgb565(th->on_surface), 30); } else { draw_text(x + w / 2 - 7, y + LK_ROW_H / 2 - 14, d, 2, th->on_surface); } } else if (key == LK_ACT_CLR) { const char *s = "C"; const char *t = s; if (mfont_ready) t = "清空"; lk_text(x + w / 2, y + LK_ROW_H / 2 - 9, t, 22, th->on_surface); } else { /* LK_ACT_BS */ const char *t = "DEL"; if (mfont_ready) t = "退格"; lk_text(x + w / 2, y + LK_ROW_H / 2 - 9, t, 22, th->on_surface); } } } meow_dirty_full(); fb_pan_buf0_force(); meow_ui_flush(); } /* 阻塞式锁屏页。返回 1=已解锁,0=锁屏期间电源键息屏。 */ int run_lock_screen(void) { int fbfd = open(FB_DEV, O_RDWR); if (fbfd >= 0) { ioctl(fbfd, FBIOBLANK, 0); close(fbfd); } /* 亮起面板 */ meow_screen_on = 1; fb_pan_buf0_force(); meow_touch mt; mt_reset(&mt); int tfd = open(DEV_TOUCH, O_RDONLY | O_NONBLOCK); struct keypoll kp_power, kp_keys; kp_open(&kp_power, DEV_POWER); kp_open(&kp_keys, DEV_KEYS); int entered = 0; char digits[LCK_PIN_LEN + 1] = {0}; char status[64] = ""; time_t lock_until = 0; { time_t rem = lck_lock_remaining(); if (rem > 0) { lock_until = time(NULL) + rem; snprintf(status, sizeof status, "已锁定"); } } time_t last_countdown = 0; /* 面板一次性解锁恢复口:进入时消费(存在即删除+放行一次,不校验 PIN/不清 PIN/不重置失败计数) */ if (lck_unlock_once_consume()) { lck_manual_lock_clear(); /* 进入系统即视为解除主动锁 */ logmsg("lock: unlock-once bypass consumed at entry -> release"); return 1; } lk_draw(entered, status, lock_until); /* 首帧 */ int unlocked = 0; for (;;) { time_t now = time(NULL); int redraw = 0; /* 面板一次性解锁恢复口:锁屏页停留期(每次重试前/每轮)也消费 */ if (lck_unlock_once_consume()) { lck_manual_lock_clear(); logmsg("lock: unlock-once bypass consumed in-loop -> release"); unlocked = 1; goto done; } /* §310 残影:锁屏叠加期 App 崩溃(僵尸)→ 中止锁屏走统一退出路径(交回 waitpid 循环 * 回收 + run_menu 重绘主屏含状态栏)。仅当 run_app_lock_overlay 置位 g_lock_overlay_pid * 才探(菜单直接锁屏无 App 可死,恒不中止)。 */ if (g_lock_overlay_pid > 0 && lck_overlay_should_abort(g_lock_overlay_pid, proc_is_zombie(g_lock_overlay_pid))) { logmsg("lock: overlay app pid %d died during lock -> abort (unified exit path)", g_lock_overlay_pid); g_lock_overlay_pid = 0; goto died; } /* 锁定到期自动解禁(清计数+戳),仅重画状态行 */ if (lock_until > 0 && now >= lock_until) { lck_lock_remaining(); lock_until = 0; status[0] = 0; redraw = 1; } if (lock_until > 0 && now != last_countdown) { last_countdown = now; time_t rem = lock_until - now; if (mfont_ready) snprintf(status, sizeof status, "已锁定,请 %d:%02d 后重试", (int)(rem / 60), (int)(rem % 60)); else snprintf(status, sizeof status, "LOCKED %d:%02d", (int)(rem / 60), (int)(rem % 60)); redraw = 1; } /* 电源键=息屏(允许再睡);音量键本页不动 */ if (kp_power.ok) { kp_poll(&kp_power, NULL, NULL); if (kp_is_down(&kp_power, KEY_POWER)) { logmsg("lock: power key -> screen off (stay locked)"); meow_panel_off(); break; /* return 0 */ } } kp_poll(&kp_keys, NULL, NULL); (void)kp_is_down(&kp_keys, KEY_VOLUMEDOWN); (void)kp_is_down(&kp_keys, KEY_VOLUMEUP); /* 触摸:只认键盘 tap(滑动手势忽略:不左缘返回、不通知中心下拉) */ if (tfd >= 0) { struct pollfd pfd = { tfd, POLLIN, 0 }; if (poll(&pfd, 1, 0) > 0) { unsigned char ev[16]; while (read(tfd, ev, sizeof ev) == (ssize_t)sizeof ev) { uint16_t type = *(uint16_t *)(ev + 8), code = *(uint16_t *)(ev + 10); int32_t val = *(int32_t *)(ev + 12); if (type == EV_ABS) { if (code == ABS_X || code == ABS_MT_POSITION_X) mt_abs(&mt, val, mt.last_y); else if (code == ABS_Y || code == ABS_MT_POSITION_Y) mt_abs(&mt, mt.last_x, val); } else if (type == EV_KEY && code == BTN_TOUCH) { if (val && !mt.down) { mt_down(&mt); } else if (!val && mt.down) { int dx, dy; if (mt_up(&mt, &dx, &dy)) { int handled = 0; for (int r = 0; r < LK_ROWS && !handled; r++) { for (int c = 0; c < LK_COLS && !handled; c++) { int x, y, w, h; lk_key_rect(r, c, &x, &y, &w, &h); if (!mt_in(&mt, x, y, w, h)) continue; handled = 1; int key = lk_keys[r][c]; if (lock_until > 0) break; /* 锁定期间键盘不响应 */ if (key >= 0) { if (entered < LCK_PIN_LEN) { digits[entered++] = (char)('0' + key); digits[entered] = 0; } } else if (key == LK_ACT_CLR) { entered = 0; digits[0] = 0; } else if (key == LK_ACT_BS) { if (entered > 0) { entered--; digits[entered] = 0; } } redraw = 1; if (entered == LCK_PIN_LEN) { if (lck_pin_check(digits)) { lck_pin_success(); lck_manual_lock_clear(); /* 主动锁标志消解 */ logmsg("lock: PIN OK -> unlock"); unlocked = 1; goto done; } else { int rem = lck_pin_fail(); entered = 0; digits[0] = 0; if (rem > 0) { lock_until = time(NULL) + rem; last_countdown = 0; snprintf(status, sizeof status, "已锁定"); } else { if (mfont_ready) snprintf(status, sizeof status, "PIN 错误"); else snprintf(status, sizeof status, "WRONG PIN"); } } } } } } } } } } } /* 整页重绘(锁屏低频交互,简单整页即可) */ if (redraw) lk_draw(entered, status, lock_until); usleep(POLL_MS * 1000); } done: if (tfd >= 0) close(tfd); kp_close(&kp_power); kp_close(&kp_keys); logmsg("lock: exit unlocked=%d screen_on=%d", unlocked, meow_screen_on); return unlocked; died: if (tfd >= 0) close(tfd); kp_close(&kp_power); kp_close(&kp_keys); logmsg("lock: exit overlay-app-died -> LCK_OVERLAY_RET_DIED"); return LCK_OVERLAY_RET_DIED; } /* 电源菜单「锁定」:**主动锁屏**(用户定案 2026-09-02 强化)。 * 语义 = 立即熄屏 + 进入锁定 + 作废挂起的面板绕过标志: * ① rm /tmp/meow_pin_unlock_once(主动锁屏作废一次性绕过); * ② 写 /tmp/meow_pin_locked(主动锁标志 → 唤醒时无论 system.pin_lock_on_wake 是否开都必锁屏); * ③ meow_panel_off() 立即熄屏(唤醒时经主屏唤醒路径弹锁屏页)。 * 不立即弹锁屏页(已熄屏);未配凭据 → no-op。返回 0(关菜单,run_menu 继续)。 */ int meow_lock_screen_engage(void) { if (!lck_pin_configured()) { logmsg("lock: no system.pin/panel.pin configured -> skip manual lock (no-op)"); return 0; } if (access(LCK_UNLOCK_ONCE_FILE, F_OK) == 0) { unlink(LCK_UNLOCK_ONCE_FILE); logmsg("lock: manual active-lock revoked pending unlock-once bypass"); } lck_manual_lock_set(); meow_panel_off(); /* 立即熄屏;唤醒时必走锁屏 */ logmsg("lock: manual active-lock -> screen off + lock on wake"); return 0; } /* ---- 设置页「锁屏 PIN」数字输入模态(system 级 PIN 设置,用 SDK 按钮画数字键盘)---- * 全屏遮罩:title + 4 圆点(●)+ 数字键盘(1-9 / C / 0 / 退格)。输入满 4 位即返回 1 并拷贝到 out * (不校验——调用方做双输确认/旧 PIN 校验);电源键=取消返回 0;音量键不动。 */ int lck_input_pin_modal(const char *title, char *out, int outcap) { meow_screen_on = 1; fb_pan_buf0_force(); meow_touch mt; mt_reset(&mt); int tfd = open(DEV_TOUCH, O_RDONLY | O_NONBLOCK); struct keypoll kp_power, kp_keys; kp_open(&kp_power, DEV_POWER); kp_open(&kp_keys, DEV_KEYS); int entered = 0; char digits[LCK_PIN_LEN + 1] = {0}; int result = 0; int dirty = 1; /* 首帧必画 */ for (;;) { /* 只在首帧/输入变化时整页重绘(消频闪:锁屏低频交互),后续轮次不动 LCDC */ if (dirty) { const meow_ui_theme_t *th = s_ui_theme(); fill_rect(0, 0, fb_xres, fb_yres, th->surface); lk_text(fb_xres / 2, 120, title, 24, th->on_surface); lk_draw_pin(entered, th); for (int r = 0; r < LK_ROWS; r++) { for (int c = 0; c < LK_COLS; c++) { int x, y, w, h; lk_key_rect(r, c, &x, &y, &w, &h); fill_round_rect(x, y, w, h, 12, th->surface_container); int key = lk_keys[r][c]; if (key >= 0) { char d[2] = { (char)('0' + key), 0 }; if (mfont_ready) { int fw = meow_font_width_text(d, 30); meow_font_drawtext((uint16_t *)fb, x + w / 2 - fw / 2, y + LK_ROW_H / 2 - meow_font_line_height(30) / 2, d, rgb565(th->on_surface), 30); } else { draw_text(x + w / 2 - 7, y + LK_ROW_H / 2 - 14, d, 2, th->on_surface); } } else if (key == LK_ACT_CLR) { const char *t2 = "C"; if (mfont_ready) t2 = "清空"; lk_text(x + w / 2, y + LK_ROW_H / 2 - 9, t2, 22, th->on_surface); } else { const char *t2 = "DEL"; if (mfont_ready) t2 = "退格"; lk_text(x + w / 2, y + LK_ROW_H / 2 - 9, t2, 22, th->on_surface); } } } meow_dirty_full(); fb_pan_buf0_force(); meow_ui_flush(); dirty = 0; } if (kp_power.ok) { kp_poll(&kp_power, NULL, NULL); if (kp_is_down(&kp_power, KEY_POWER)) { result = 0; break; } /* 取消 */ } kp_poll(&kp_keys, NULL, NULL); (void)kp_is_down(&kp_keys, KEY_VOLUMEDOWN); (void)kp_is_down(&kp_keys, KEY_VOLUMEUP); if (tfd >= 0) { struct pollfd pfd = { tfd, POLLIN, 0 }; if (poll(&pfd, 1, 0) > 0) { unsigned char ev[16]; while (read(tfd, ev, sizeof ev) == (ssize_t)sizeof ev) { uint16_t type = *(uint16_t *)(ev + 8), code = *(uint16_t *)(ev + 10); int32_t val = *(int32_t *)(ev + 12); if (type == EV_ABS) { if (code == ABS_X || code == ABS_MT_POSITION_X) mt_abs(&mt, val, mt.last_y); else if (code == ABS_Y || code == ABS_MT_POSITION_Y) mt_abs(&mt, mt.last_x, val); } else if (type == EV_KEY && code == BTN_TOUCH) { if (val && !mt.down) { mt_down(&mt); } else if (!val && mt.down) { int dx, dy; if (mt_up(&mt, &dx, &dy)) { for (int r = 0; r < LK_ROWS; r++) { for (int c = 0; c < LK_COLS; c++) { int x, y, w, h; lk_key_rect(r, c, &x, &y, &w, &h); if (!mt_in(&mt, x, y, w, h)) continue; int key = lk_keys[r][c]; if (key >= 0) { if (entered < LCK_PIN_LEN) { digits[entered++] = (char)('0' + key); digits[entered] = 0; } } else if (key == LK_ACT_CLR) { entered = 0; digits[0] = 0; } else if (key == LK_ACT_BS) { if (entered > 0) { entered--; digits[entered] = 0; } } dirty = 1; if (entered == LCK_PIN_LEN) { if (out && outcap > LCK_PIN_LEN) { snprintf(out, (size_t)outcap, "%s", digits); result = 1; } else result = 1; goto done2; } break; } } } } } } } } usleep(POLL_MS * 1000); } done2: if (tfd >= 0) close(tfd); kp_close(&kp_power); kp_close(&kp_keys); return result; }