Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/lock_screen_ui.c
T

748 lines
39 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.
/* 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"
#include "overlay_gate.h" /* OVERLAY_LOCK_COVER_MS(锁屏重铺阈值,与单测同源) */
/* §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 },
};
/* ---- M7.1b 锁屏控件化:PIN 数字键盘 / 时钟 / 提示 / PIN 圆点 改用 SDK 控件 ----
* 旧自绘锁屏 UI 保留在 #if 0(回退位,见 lk_draw/lk_key_hit),新 SDK 渲染默认启用;
* run_lock_screen 会话骨架(EVIOCGRAB / 自睡 / 解锁脏区修复 / 物理键转发)不动,只替换
* 它调用的绘制/命中函数体。控件 owner 钉 LK_SENTINEL_ACT(子页 0x52 之外的独立哨兵)。 */
#define LK_SENTINEL_ACT 0x51 /* 锁屏控件 owner 哨兵(与子页 SUB_SENTINEL_ACT=0x52 区分) */
#define LK_DOWN_NONE (-10) /* 无键按下哨兵(关键值范围 LK_ACT_CLR=-2/-1/0-9) */
static meow_widget *lk_key_w[LK_ROWS][LK_COLS]; /* 12 键按钮(meow_widget_custom) */
static int lk_widgets_ready = 0;
static int g_lk_down_key = LK_DOWN_NONE; /* 当前按下的键(跟手指按压态) */
/* 键盘按钮 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);
}
/* ---- M7.1b 控件化:SDK meow_widget 键盘 + meow_font 文字 ---- */
/* 数字键标签(1-9/0 单字符;static 缓冲各调用立即消费,无跨调用残留) */
static const char *lk_key_digit(int key)
{
static char d[2];
d[0] = (char)('0' + key);
d[1] = 0;
return d;
}
/* 键渲染:surfaceContainer 圆角底(MD3)+ 按压态 primary 26% α + 居中标签(SDK meow_font)。
* 颜色全部取 meow_theme_get()(SDK 单一权威:MD3/MIUIx × 明暗自动);读 w->state 的
* MEOW_WS_PRESSED 位(跟手指,由 lk_set_down_key 置/清)。 */
static void lk_key_render(meow_widget *w, const meow_theme *th, void *ctx)
{
int key = (int)(intptr_t)ctx;
int rc[4]; meow_widget_rect(w, rc); /* 绝对坐标 x,y,w,h(控件不透明,用 SDK 只读几何) */
int x = rc[0], y = rc[1], ww = rc[2], hh = rc[3];
unsigned st = meow_widget_state(w);
int rad = (th->radius > 0) ? th->radius : 10;
meow_gfx_fill_round(x, y, ww, hh, rad, th->surfaceContainer);
if (st & MEOW_WS_PRESSED)
meow_gfx_fill_round_alpha(x, y, ww, hh, rad, th->primary, 66);
const char *label;
int size;
if (key >= 0) { size = 30; label = lk_key_digit(key); }
else if (key == LK_ACT_CLR) { size = 22; label = mfont_ready ? meow_str_zh("lock.clear", "清空") : "C"; }
else { size = 22; label = mfont_ready ? meow_str_zh("lock.backspace", "退格") : "DEL"; }
int cx = x + ww / 2;
int cy = y + hh / 2 - meow_ui_text_height(size) / 2;
meow_ui_text_center(cx, cy, label, size, th->onSurface);
}
/* 懒创建 12 键(owner=LK_SENTINEL_ACT;仅首次调用建一次)。布局几何沿用 lk_key_rect。 */
static void lk_widgets_build(void)
{
if (lk_widgets_ready) return;
meow_ui_activity_push(LK_SENTINEL_ACT);
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);
lk_key_w[r][c] = meow_widget_custom_create(x, y, w, h, lk_key_render, NULL,
(void *)(intptr_t)lk_keys[r][c]);
}
}
meow_ui_activity_pop();
lk_widgets_ready = 1;
}
/* 键值 -> 按钮(按压态置/清位用)。 */
static meow_widget *lk_widget_for_key(int key)
{
for (int r = 0; r < LK_ROWS; r++)
for (int c = 0; c < LK_COLS; c++)
if (lk_keys[r][c] == key) return lk_key_w[r][c];
return NULL;
}
/* 坐标 -> 键值(SDK meow_widget_hit;owner==active 已由调用方保证)。未命中返回 LK_DOWN_NONE。 */
static int lk_key_value_at(int x, int y)
{
for (int r = 0; r < LK_ROWS; r++)
for (int c = 0; c < LK_COLS; c++)
if (lk_key_w[r][c] && meow_widget_hit(lk_key_w[r][c], x, y))
return lk_keys[r][c];
return LK_DOWN_NONE;
}
/* 更新按压态(跟手指):清旧键状态位、置新键;返回 1=按键变化(调用方据此重绘)。 */
static int lk_set_down_key(int key)
{
if (key == g_lk_down_key) return 0;
meow_widget *old = lk_widget_for_key(g_lk_down_key);
if (old) meow_widget_set_state(old, MEOW_WS_PRESSED, 0);
meow_widget *nw = lk_widget_for_key(key);
if (nw) meow_widget_set_state(nw, MEOW_WS_PRESSED, 1);
g_lk_down_key = key;
return 1;
}
/* PIN 输入圆点(● 遮罩:已输入=primary 实心;未输入=primary 环 + surface 孔)。 */
static void lk_draw_pin_dots(int entered, const meow_theme *mt)
{
int cx = fb_xres / 2;
int y = 210;
int gap = 48, r = 20;
int x0 = cx - (3 * gap) / 2;
for (int i = 0; i < LCK_PIN_LEN; i++) {
int dx = x0 + i * gap;
fill_circle(dx, y, r, mt->primary);
if (i >= entered) fill_circle(dx, y, r - 3, mt->surface);
}
}
/* 键命中:旧自绘(lk_key_rect+mt_in)保留在 #if 0(回退位);M7.1b 走 SDK meow_widget_hit
* (owner==active + enabled 双闸门在 SDK 内;命中点=按下原点 mt->down_x/down_y,与原 mt_in 同源)。 */
#if 0
static int lk_key_hit(const meow_touch *mt, int row, int col)
{
int x, y, w, h; lk_key_rect(row, col, &x, &y, &w, &h);
return mt_in(mt, x, y, w, h);
}
#else
static int lk_key_hit(const meow_touch *mt, int row, int col)
{
if (!lk_key_w[row][col]) return 0;
return meow_widget_hit(lk_key_w[row][col], mt->down_x, mt->down_y);
}
#endif
#if 0
/* ============ 旧自绘锁屏整帧(M7.1b 回退位)============
* 原 lk_draw:fill_rect 全屏底 + lk_text(时钟/状态)+ lk_draw_pin(●)+ fill_round_rect 键盘。
* 一键回退:把下面 #if 0 改成 #if 1(并把新 lk_draw 的 #else 分支由 #else 改走旧轮廓),设备即回旧观感。 */
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();
}
#else
/* ============ M7.1b 锁屏控件化整帧(默认启用)============
* 整帧不透明 surface 底(§310 脏区修复关键:全屏覆盖,无 App 帧透出)+ SDK 字体时钟/提示
* + 主题色 PIN 圆点 + meow_widget 键盘(surfaceContainer 圆角 / 按压态 primary 26% α / MD3 主题)。
* 颜色全部取 meow_theme_get()(SDK 单一权威:MD3/MIUIx × 明暗自动)。 */
static void lk_draw(int entered, const char *status, time_t locked_until)
{
const meow_theme *mt = meow_theme_get();
meow_ui_fill_rect(0, 0, fb_xres, fb_yres, mt->surface); /* 全屏不透明表面底 */
lk_widgets_build(); /* 懒创建 12 键 */
/* 时钟/提示(居中,SDK meow_font):锁定倒计时标题 */
meow_ui_text_center(fb_xres / 2, 120, locked_until > 0 ? meow_str_zh("lock.locked", "已锁定")
: meow_str_zh("lock.prompt", "输入 PIN 解锁"),
24, mt->onSurface);
/* PIN 输入圆点(● 遮罩) */
lk_draw_pin_dots(entered, mt);
/* 状态行:错误提示(PIN 错误)着 error;倒计时/其它信息用 onSurfaceVariant */
int is_err = status[0] && (strstr(status, meow_str_zh("lock.pin_wrong", "PIN 错误")) != NULL ||
strstr(status, "WRONG PIN") != NULL);
uint32_t status_col = status[0] ? (is_err ? mt->error : mt->onSurfaceVariant)
: mt->onSurfaceVariant;
meow_ui_text_center(fb_xres / 2, 260, status, 18, status_col);
/* 12 键(SDK meow_widget;命中走 lk_key_hit → meow_widget_hit) */
for (int r = 0; r < LK_ROWS; r++)
for (int c = 0; c < LK_COLS; c++)
if (lk_key_w[r][c]) meow_widget_draw(lk_key_w[r][c]);
meow_dirty_full();
fb_pan_buf0_force();
meow_ui_flush();
}
#endif
/* 阻塞式锁屏页。返回 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);
/* §310 输入穿透(媒体保活 App 不被 SIGSTOP,实时读 evdev):锁屏页独占触控设备——
* EVIOCGRAB 使本 fd 之外的其它进程(保活 App 的 SDK 输入线程 / watchdog 手势)在该设备
* 上收不到事件(事件仅投递抓取者),且抓取期事件不会为未抓取 fd 缓冲/回放。由此无论 App
* 冻结与否,锁屏期**任何** App 都收不到触摸(真全模态)。失败(驱动不支持)则记日志降级,
* 仍靠 keep_running/SIGSTOP+标记隔离。关闭 tfd 时抓取自动解除(仍显式释放一次)。 */
if (tfd >= 0) {
int grab = 1;
if (ioctl(tfd, EVIOCGRAB, &grab) < 0)
logmsg("lock: EVIOCGRAB touch fail: %s (graceful degrade, marker+SIGSTOP isola only)",
strerror(errno));
}
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, "%s", meow_str_zh("lock.locked", "已锁定")); }
}
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;
}
/* M7.1 解锁恢复:锁屏是整帧不透明覆盖(lk_draw fill_rect 全屏),退出后底层界面不会自动重绘
* —— 只有 App 自绘的控件区域刷新,未刷新区残留锁屏 PIN 最后一帧(解锁后整屏脏区根因)。此处
* 在锁屏首扑前把底层帧(SDK back_buf = 进入前的 App 帧/主界面帧)快照下来;解锁(unlocked=1)
* 时整帧回写 back_buf + present,强制底层界面完整重绘(app_launcher.c 的 lock-overlay 关闭路径
* 随后 pm_capture_app_frame 读 fb0 即得到正确 App 帧,不再读到锁帧)。 */
unsigned char *lk_bg_snap = NULL;
size_t lk_bg_len = 0;
if (back_buf_ok && fb && fb_line > 0 && fb_yres > 0) {
lk_bg_len = (size_t)fb_line * (size_t)fb_yres;
lk_bg_snap = malloc(lk_bg_len);
if (lk_bg_snap) memcpy(lk_bg_snap, fb, lk_bg_len);
else { lk_bg_len = 0; logmsg("lock: bg snapshot malloc fail (len=%zu)", lk_bg_len); }
}
/* M7.1b:锁屏控件 owner 闸门打开(meow_widget_hit / 按压态需 active==LK_SENTINEL_ACT)。
* 会话骨架(EVIOCGRAB/自睡/解锁脏区修复)不受影响;done/died 处对称 pop。 */
meow_ui_activity_push(LK_SENTINEL_ACT);
g_lk_down_key = LK_DOWN_NONE;
lk_draw(entered, status, lock_until); /* 首帧(全屏不透明主题底 + 控件化键盘) */
int unlocked = 0;
unsigned long long entry_ms = now_ms();
int covered_once = 0; /* §310 问题2-②:标记节流窗后重铺一次全屏底,盖掉 App 残帧 */
/* §310 M6.7 锁屏页自睡(294 遗留):锁屏页不应常亮耗电。
* 阈值 = min(系统 screen_off_timeout, 30s):系统未设/设 0(常亮)/超 30 → 锁屏页封顶 30s,
* 系统设更短则尊重更短。超阈值 → meow_panel_off()(与息屏路径一致)+ asleep=1(锁态保持,
* 仍留在锁屏循环);asleep 后电源键按下边沿/触摸活动 → meow_panel_on() + 重画锁屏页回锁。 */
int lk_off_sec = overlay_lock_idle_off_sec(screen_off_timeout);
unsigned long long lk_off_ms = (unsigned long long)lk_off_sec * 1000;
unsigned long long last_act = now_ms();
int asleep = 0;
int prev_pwr_down = 0;
/* M7.1 锁屏期物理键保留语义(音量/媒体键):边沿跟踪基线(锁屏是 EVIOCGRAB 独占真模态,
* 这些键只能在本层显式处理,不会转发到系统)。 */
int prev_vold = 0, prev_volu = 0, prev_play = 0, prev_prv = 0, prev_nxt = 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 问题2-②:媒体保活 App 不冻结、SDK present 抑制有 ≤200ms 标记节流窗——锁屏首扑后
* App 仍可能再进 1-2 帧(部分脏区盖到锁屏上)。等标记窗过后(≥300ms)重铺一次全屏不透明底
* 盖掉 App 残帧;App 此后被抑制不再上屏,锁屏稳定遮盖(真机「App 控件盖到锁屏上」)。 */
if (!covered_once && !asleep && now_ms() - entry_ms >= OVERLAY_LOCK_COVER_MS) {
covered_once = 1;
logmsg("lock: re-cover full opaque after marker window (app stave suppressed)");
lk_draw(entered, status, lock_until);
}
/* §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, meow_str_zh("lock.locked_wait", "已锁定,请 %d:%02d 后重试"),
(int)(rem / 60), (int)(rem % 60));
else snprintf(status, sizeof status, "LOCKED %d:%02d", (int)(rem / 60), (int)(rem % 60));
redraw = 1;
}
/* 电源键/音量键轮询 + 输入活动检测(重置自睡计时) + 自睡/唤醒切换。
* 活跃态:电源键按下 → 息屏并退出锁屏(回 App/主屏,下次唤醒再锁)——沿用原语义;
* 空闲超 lk_off_ms → 自睡(asleep=1,锁态保持,仍在本循环)。
* asleep:电源键按下边沿 / 触摸活动 → meow_panel_on + 重画锁屏页(本循环唤醒链回锁)。
* 音量键本页不动(仅视为输入活动,重置计时)。 */
{
int chg = 0;
if (kp_power.ok) chg |= kp_poll(&kp_power, NULL, NULL);
chg |= kp_poll(&kp_keys, NULL, NULL);
if (chg) last_act = now_ms(); /* 任意按键活动重置自睡计时 */
int pwr_down = kp_is_down(&kp_power, KEY_POWER);
int pwr_edge = pwr_down && !prev_pwr_down; /* 按下边沿 */
prev_pwr_down = pwr_down;
if (asleep) {
int touch_ready = (tfd >= 0);
if (touch_ready) {
struct pollfd tp = { tfd, POLLIN, 0 };
if (poll(&tp, 1, 0) <= 0) touch_ready = 0;
}
if (pwr_edge || touch_ready) {
logmsg("lock: wake from self-sleep (power=%d touch=%d)", pwr_edge, touch_ready);
meow_panel_on();
asleep = 0;
last_act = now_ms();
g_lk_down_key = LK_DOWN_NONE; /* M7.1b:唤醒复位按压态基线 */
lk_draw(entered, status, lock_until); /* 重画锁屏页回锁 */
continue;
}
} else {
/* 电源键=息屏(允许再睡;下次唤醒按 lck_wake_should_lock 再锁) */
if (pwr_down) {
logmsg("lock: power key -> screen off (stay locked)");
meow_panel_off();
break; /* return 0 */
}
/* M7.1 锁屏期物理键仍执行其语义(用户定案:音量键调音量、媒体键控播放/上下曲)。
* 锁屏层 EVIOCGRAB 独占这些输入(真模态),不转发系统,故必须在此显式处理,不能依赖别处。
* 音量沿用 launcher 既有递增/递减路径(settings_volume_adj —— 起 1% 线性渐变,
* 长按由下方 vol_ramp_tick 每圈推进);播放/上下曲经 meow_media_playctl 下发媒体指令。 */
int vold = (kp_keys.ok) && kp_is_down(&kp_keys, KEY_VOLUMEDOWN);
int volu = (kp_keys.ok) && kp_is_down(&kp_keys, KEY_VOLUMEUP);
int play = (kp_keys.ok) && kp_is_down(&kp_keys, KEY_PLAYPAUSE);
int prv = (kp_keys.ok && kp_is_down(&kp_keys, KEY_PREVIOUSSONG)) ||
(kp_power.ok && kp_is_down(&kp_power, KEY_PREVIOUSSONG));
int nxt = (kp_keys.ok && kp_is_down(&kp_keys, KEY_NEXT)) ||
(kp_power.ok && kp_is_down(&kp_power, KEY_NEXT));
if (vold && !prev_vold) { settings_volume_adj(-settings_volume_step); last_act = now_ms(); }
if (volu && !prev_volu) { settings_volume_adj(+settings_volume_step); last_act = now_ms(); }
if (play && !prev_play) {
const meow_media_session_t *ms = meow_media_current();
if (ms) {
uint32_t c = (ms->state == MEOW_MEDIA_PLAYING) ? 2u : 1u; /* 暂停/播放 */
int rc = meow_media_playctl(c, 0);
logmsg("lock: play key -> media cmd=%u rc=%d (%s)", c, rc, ms->app_id);
} else {
logmsg("lock: play key -> media-reserved (no media source)");
}
last_act = now_ms();
}
if (prv && !prev_prv) {
if (meow_media_current()) { int rc = meow_media_playctl(3, 0); logmsg("lock: prev key -> cmd=3 rc=%d", rc); }
else logmsg("lock: prev key -> media-reserved (no media source)");
last_act = now_ms();
}
if (nxt && !prev_nxt) {
if (meow_media_current()) { int rc = meow_media_playctl(4, 0); logmsg("lock: next key -> cmd=4 rc=%d", rc); }
else logmsg("lock: next key -> media-reserved (no media source)");
last_act = now_ms();
}
prev_vold = vold; prev_volu = volu; prev_play = play; prev_prv = prv; prev_nxt = nxt;
/* 长按音量键线性渐变(同主菜单 vol_ramp 路径,每圈推进 1%)。锁屏态不刷状态栏
* (状态栏被锁屏页覆盖),仅保持真实音量/DAC 同步。 */
if (vol_ramp_dir != 0) {
int held = (vol_ramp_dir > 0)
? (kp_keys.ok && kp_is_down(&kp_keys, KEY_VOLUMEUP))
: (kp_keys.ok && kp_is_down(&kp_keys, KEY_VOLUMEDOWN));
int going = vol_ramp_tick(held);
if (!going) logmsg("lock: volume ramp end -> %d", settings_volume_get());
}
/* 空闲超时 → 自睡(锁态保持,仍在锁屏循环,唤醒回锁屏页) */
if (meow_screen_on && now_ms() - last_act >= lk_off_ms) {
logmsg("lock: idle %ds -> self screen off (stay locked)", lk_off_sec);
meow_panel_off();
asleep = 1;
continue;
}
}
}
/* 触摸:只认键盘 tap(滑动手势忽略:不左缘返回、不通知中心下拉);触摸即输入活动,重置自睡计时 */
if (tfd >= 0 && !asleep) {
struct pollfd pfd = { tfd, POLLIN, 0 };
if (poll(&pfd, 1, 0) > 0) {
last_act = now_ms(); /* §310 M6.7:任意触摸重置自睡计时 */
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);
/* M7.1b 跟手指按压态:down 且两轴有效才定键(goodix 时序免疫);
* 键变化(滑动换键)即时重绘按压高亮。 */
if (mt.down && mt.last_x >= 0 && mt.last_y >= 0) {
if (lk_set_down_key(lk_key_value_at(mt.last_x, mt.last_y)))
redraw = 1;
}
} else if (type == EV_KEY && code == BTN_TOUCH) {
if (val && !mt.down) {
mt_down(&mt);
} else if (!val && mt.down) {
lk_set_down_key(LK_DOWN_NONE); /* M7.1b:抬手清按压态 */
redraw = 1;
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++) {
if (!lk_key_hit(&mt, r, c)) continue; /* M7.1b SDK 控件命中 */
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, "%s", meow_str_zh("lock.locked", "已锁定"));
} else {
if (mfont_ready) snprintf(status, sizeof status, "%s", meow_str_zh("lock.pin_wrong", "PIN 错误"));
else snprintf(status, sizeof status, "WRONG PIN");
}
}
}
}
}
}
}
}
}
}
}
/* 整页重绘(锁屏低频交互,简单整页即可);自睡(面板已关)期间不重绘(唤醒时统一重画) */
if (redraw && !asleep) lk_draw(entered, status, lock_until);
usleep(POLL_MS * 1000);
}
done:
meow_ui_activity_pop(); /* M7.1b:锁屏控件 owner 闸门关闭(与进场 push 对称) */
g_lk_down_key = LK_DOWN_NONE;
/* M7.1 解锁恢复:把锁屏首扑前快照的底层帧整帧回写 back_buf + present,强制底层 App/主界面
* 完整重绘(消除「解锁后整屏 = PIN 最后一帧,仅 App 控件自绘区刷新」的脏区残留)。 */
if (unlocked && lk_bg_snap && back_buf_ok && fb && lk_bg_len > 0) {
memcpy(fb, lk_bg_snap, lk_bg_len);
meow_dirty_full();
meow_ui_flush();
logmsg("lock: unlock -> restore underlying frame (full repaint)");
}
if (lk_bg_snap) { free(lk_bg_snap); lk_bg_snap = NULL; lk_bg_len = 0; }
if (tfd >= 0) {
int grab = 0; /* 显式释放 EVIOCGRAB(关闭 tfd 也会自动解除,双保险) */
ioctl(tfd, EVIOCGRAB, &grab);
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:
meow_ui_activity_pop(); /* M7.1b:锁屏控件 owner 闸门关闭(与进场 push 对称) */
g_lk_down_key = LK_DOWN_NONE;
if (lk_bg_snap) { free(lk_bg_snap); lk_bg_snap = NULL; lk_bg_len = 0; }
if (tfd >= 0) {
int grab = 0;
ioctl(tfd, EVIOCGRAB, &grab);
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);
/* §310 输入穿透:设置页 PIN 模态同样独占触控设备(后台保活 App 不冻结实时读 evdev,防其
* 同坐标控件被 PIN 键盘点击双重触发)。失败降级(仍靠标记+SIGSTOP 隔离)。 */
if (tfd >= 0) {
int grab = 1;
if (ioctl(tfd, EVIOCGRAB, &grab) < 0)
logmsg("pin-modal: EVIOCGRAB touch fail: %s (graceful degrade)", strerror(errno));
}
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 = meow_str_zh("lock.clear", "清空");
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 = meow_str_zh("lock.backspace", "退格");
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) {
int grab = 0; /* 显式释放 EVIOCGRAB(关闭 tfd 亦自动解除,双保险) */
ioctl(tfd, EVIOCGRAB, &grab);
close(tfd);
}
kp_close(&kp_power);
kp_close(&kp_keys);
return result;
}