Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/subpage.c
T
AskaEth 7c7d891570 feat: M6.44+M6.45 收编 — 真机 0.8.45 现场保全(reader章节/key_router按键路由/WiFi连接修复/ncm bench+IME修复)
脏工作区现场收编:devdocs/local/m6_44_45_dirty_tracked.patch(46 个已跟踪文本文件,
+3090/-585, 已对 M6.43 基线干净适配) + m6_44_45_untracked_src.tar.gz 中 patch_v2/ 下
13 个新增源文件, 合成单个提交, 内容与容器工作树(R3ProII/patch_v2)逐字节一致。

- reader 章节: 新增 core/rdchapter.{h,c} — TXT 章节规范解析器, 扫描 512KB 码位对齐段文件
  逐段流式识别标题行(行首缩进≤4/显示宽≤40/行内标点≤1 三重约束防误判), 输出标题+全书字符
  偏移+段号/段内偏移+阿拉伯章号(中文数字归一), 首章前内容归"卷首"; book/reader/rdenc/
  rdlayout/settings 接入章节跳转与进度锚点, 附 chapter_smoke/turn_repro/build_turn_repro.sh
- key_router 按键路由: 新增 meow_launcherd/key_router.{h,c} — launcher-UI 页物理键统一路由
  (P1 仅音量键), 把读取边沿+防抖+长按 held+ramp 推进从 5 个会话收敛为进程内单开;
  各页每圈 key_router_pump() 取防抖后边沿, 动作(调音量/光标导航)仍由调用页决定,
  保留 run_menu 的 meow_mode 分支; App 前台 volume_page 武装时边沿抑制;
  main_menu/now_playing/notif_center/settings_page/subpage 接入, 附 test_key_router_vol/test_np_keys
- WiFi 连接修复: meow_netmgrd connect 有界等待 wpa_state=COMPLETED(普通 10s/隐藏网络 15s,
  env 可调)修设置页连接无响应; list_networks/scan_results 的 \xNN 转义还原始字节, 修中文 SSID
  永不命中致新建同名 open 记录顶影; launcher netmgr_ipc 配读超时; 附 test_wifi_connect(桩
  wpa_cli 驱动 static wifi_connect_ex), test_netmgrd.sh 补用例; wifi_page 同步
- ncm bench: 新增 bench/lyric_sim.c、bench/plpage_sim.c, vol_sync_sim 与 ncm core/app 同步
- SDK IME 修复: shift 大小写、K_ENTER 只提交不关、阻塞 IME 循环喂 on_tick 不被遮挡、
  IME 打开 ~200ms TAP 冷却窗吞 stray-TAP; 附 sdk/test_ime_fix.c
- 同期: meow_audiod 音量异步 ACK 线程(代际防 fd/槽复用竞态)、watchdog 音量收编(首选 audiod
  权威, settings.conf 覆盖路径)、panel 文件搜索(F1-④)与蓝牙只读详情(F1-③)、各类 build 脚本同步

范围红线: third_party/legado-E-main 原开发者未跟踪, 本次不入库(保留 devdocs/local tar 参考);
二进制不入库(后续重编); devdocs/ 不入库。59 文件全部在 patch_v2/ 下, 无 third_party/devdocs。
验证: git apply --check 干净; 59 文件 md5 与容器 patch_v2 逐一比对 59/59 一致(含
key_router.c/rdchapter.c/meow_ui_ime.c/test_wifi_connect.c 抽查); 可执行位保守
(check_lgpl_release.sh 100755, build_turn_repro.sh 100644 与容器一致)。
2026-11-04 20:02:28 +08:00

1086 lines
57 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.
/* subpage.c — MeowOS launcher 独立子页面(WiFi/蓝牙/锁屏 PIN)统一脚手架(M2.76)。
*
* 安卓风子页骨架:顶栏(标题 + 返回 + 右上开关)+ 分组列表(组标题/行/开关/动作)
* + 滚动 + 左缘左滑返回。
*
* 由页面(wifi_page.c / bt_page.c / lock_settings.c)通过 sub_page_t 提供:
* - title 顶栏标题
* - top_sw 顶栏开关(0=不显示;1=显示并回写当前值)
* - top_sw_set 开关切换(写设置 + 生效 + 触发重扫)
* - rows 填行(组标题/普通行/开关行/动作行),返回行数
* - act 点动作/开关行
* - detail 点普通行(如当前连接详情、网络/设备详情)
* - live 1=驻留时周期性(约2s)重扫行(蓝牙附近扫描等动态数据)
*
* ================================================================
* M7.1a 渲染层迁移:脚手架手绘(顶栏 + 分组列表 + 跟手滚动 + 手绘 switch/chevron)
* 改走 SDK 控件(单一 meow_theme 权威):
* - 顶栏 → meow_topappbar(标题 + 返回箭头 + 底部分隔线);
* - 顶部开关 → meow_switch(叠顶栏右缘,保留开/关视觉与命中);
* - 分组列表 → meow_listview(virtualized set_vsource 固定 item 池 + section 段头
* + right=SWITCH/ARROW 尾件 + 选中/按压态);
* - 滚动 → meow_scroll(跟手 track 采样松手 fling + snap 到行边界)。
* wifi/bt/lock_settings 的「数据 + 动作」逻辑(netmgr IPC / bluetoothctl / 设置契约)
* 全部保留在 launcher 逻辑层,仅换渲染。旧手绘路径整体保留 #if 0 作回退位(M7 红线)。
*
* 与设置页同主题(s_ui_theme → SDK meow_theme 单一权威,MD3/MIUIx×明暗自动);
* 模态弹窗交给 launcher 既有 s_dd_modal(点外关闭不穿透),本脚手架在回调返回后
* 彻底排空本页 tfd + 重置触摸基线(复刻 run_settings_page 后置排空,防穿透)。
*
* 物理键:电源=系统级语义(单击息屏/亮屏、长按电源菜单、8s 强关)、音量=系统音量、
* 播放=抓住——不再映射为返回(APPS §3.4.1 全局接管)。
*/
#include "meow_launcher_internal.h"
/* ---- 布局常量(顶栏/内容区;全屏内容区延用设置页 surface 整屏底)----
* note:SUB_CONT_TOP 与旧版一致(96),顶栏底缘分隔线由 SDK topappbar 绘制(y=95)。 */
#define SUB_TB_Y SB_BAR_H /* 28:顶栏顶缘(状态栏下) */
#define SUB_TB_H 68 /* 顶栏高 28..96 */
#define SUB_TB_BOT (SUB_TB_Y + SUB_TB_H) /* 96:顶栏底缘 */
#define SUB_CONT_TOP (SUB_TB_BOT) /* 96:内容区顶缘(topappbar 分隔线下) */
#define SUB_FOOT_H 44 /* 底部版本信息带(右对齐) */
#define SUB_ITEM_H 56 /* MD3 行高(双行富 item:主/副标题) */
#define SUB_PAD_X 16 /* 水平内边距(顶开关右缘留白用) */
#define SUB_SW_H 32 /* 顶开关高(meow_switch 固定 56×32) */
#if 0 /* ================================================================
* M7.1a 退役:子页旧手绘脚手架(顶栏 + 分组列表 + 跟手无惯性滚动 + 手绘
* s_draw_switch/s_draw_chevron + 自算行前缀和 + 自算命中)。改走 SDK 控件
* (meow_topappbar + meow_listview + meow_scroll),命中/布局/状态/滚动/主题
* 全由 SDK 接管。本块保留作回退位(删除 #if 0 行并删下方「M7.1a SDK 路径」段
* 即可恢复旧手绘)。以下全部内容与迁移前 subpage.c 逐字一致。
* ================================================================ */
#define SUB_ROW_H 58 /* 行高(对齐 SROW_H,便于触摸) */
#define SUB_HDR_H 40 /* 组标题行高 */
#define SUB_SW_W 48 /* 开关宽(s_draw_switch) */
#define SUB_BTN_X 8 /* 底部返回按钮左缘 */
#define SUB_BTN_W 120 /* 底部返回按钮宽 */
/* 返回行高:组标题 40,其余 58 */
static int sub_row_h(const sub_row_t *r)
{
return (r->kind == SUB_HDR) ? SUB_HDR_H : SUB_ROW_H;
}
/* 滚动 clamp 到 [0, max](scroll_max 由调用方保证 >=0) */
static int sub_clamp(int v, int max)
{
if (v < 0) v = 0;
if (v > max) v = max;
return v;
}
/* ---- 计算行 y 前缀和(ypos[i]=第 i 行顶缘相对内容区顶;ypos[n]=总高)---- */
static void sub_ypos(const sub_row_t *rows, int n, int ypos[SUB_ROWS_MAX + 1])
{
ypos[0] = 0;
for (int i = 0; i < n; i++) ypos[i + 1] = ypos[i] + sub_row_h(&rows[i]);
}
/* ---- 绘制子页(进 back_buf;调用方再局部标脏 + flush)----
* rows/nrows 为当前行集;scroll_off 为滚动偏移;top_on 为顶栏开关当前值;
* top_sw_en 为是否显示顶栏开关。 */
static void subpage_paint(sub_page_t *p, const sub_row_t *rows, int nrows,
int scroll_off, int top_on, int top_sw_en)
{
const meow_ui_theme_t *th = s_ui_theme();
int W = fb_xres, H = fb_yres;
int ypos[SUB_ROWS_MAX + 1];
sub_ypos(rows, nrows, ypos);
int total = ypos[nrows];
int view_h = H - SUB_CONT_TOP;
int scroll_max = total > view_h ? total - view_h : 0;
/* 全屏底 surface + 顶栏区 */
fill_rect(0, 0, W, H, th->surface);
draw_statusbar();
/* 顶栏:返回箭头 + 标题 + 右上开关;顶栏底缘 outline 分隔线 */
int tb_cy = SUB_TB_Y + SUB_TB_H / 2; /* 顶栏垂直中心 */
s_draw_chevron_left(SUB_PAD_X, tb_cy, th->on_surface);
if (p->title) ui_draw(SUB_PAD_X + 24, tb_cy - meow_font_line_height(22) / 2,
p->title, th->on_surface, 22);
if (top_sw_en) {
int sx = W - SUB_PAD_X - SUB_SW_W;
int sy = tb_cy - SUB_SW_H / 2;
s_draw_switch(sx, sy, top_on, th);
}
fill_rect(0, SUB_TB_BOT, W, 2, th->outline);
/* 内容区(surface_variant 整屏底) */
fill_rect(0, SUB_TB_BOT, W, H - SUB_TB_BOT, th->surface_variant);
/* 分组列表行(裁剪到视口;顶部状态栏/顶栏、底部返回区不画) */
for (int i = 0; i < nrows; i++) {
int ry = SUB_CONT_TOP + ypos[i] - scroll_off;
int rh = sub_row_h(&rows[i]);
if (ry + rh < SUB_CONT_TOP || ry >= H) continue; /* 视口外整行跳过(配滚动) */
const sub_row_t *r = &rows[i];
if (r->kind == SUB_HDR) {
ui_draw(SUB_PAD_X + 4, ry + (SUB_HDR_H - meow_font_line_height(18)) / 2,
r->label ? r->label : "", th->on_surface_variant, 18);
} else {
int ly = ry + (SUB_ROW_H - meow_font_line_height(22)) / 2;
ui_draw(SUB_PAD_X + 4, ly, r->label ? r->label : "", th->on_surface, 22);
if (r->sub && r->sub[0]) {
int vw = ui_width(r->sub, 22);
if (r->kind == SUB_ACT) vw += 8; /* 给右 chevron 留位 */
ui_draw(W - SUB_PAD_X - 12 - vw, ly, r->sub, th->on_surface_variant, 22);
}
if (r->kind == SUB_SW) {
int sy = ry + (SUB_ROW_H - SUB_SW_H) / 2;
s_draw_switch(W - SUB_PAD_X - SUB_SW_W, sy, r->on, th);
} else if (r->kind == SUB_ACT) {
int cy = ry + SUB_ROW_H / 2;
s_draw_chevron(W - SUB_PAD_X - 6, cy, th->on_surface_variant);
}
/* 行底分隔线(on_surface_outline 弱化) */
meow_ui_fill_rect(SUB_PAD_X + 4, ry + SUB_ROW_H - 1, W - 2 * (SUB_PAD_X + 4), 1,
th->outline);
}
}
/* 滚动条(内容超出视口时) */
if (scroll_max > 0) {
int thumb_h = view_h * view_h / total;
if (thumb_h < 24) thumb_h = 24;
int th_y = SUB_CONT_TOP + (view_h - thumb_h) * scroll_off / (total - view_h);
meow_gfx_fill_round(W - 12, SUB_CONT_TOP, 5, view_h, 3, th->surface_container);
meow_gfx_fill_round(W - 12, th_y, 5, thumb_h, 3, th->on_surface_variant);
}
/* 底部返回按钮(左)+ 版本号(右),复用设置页底部对齐 */
{
int foot_y = H - meow_font_line_height(20) - 8;
s_draw_chevron_left(SUB_PAD_X + 4, foot_y + meow_font_line_height(20) / 2,
th->on_surface_variant);
ui_draw(SUB_PAD_X + 16, foot_y, "返回", th->on_surface_variant, 20);
char ver[32];
snprintf(ver, sizeof ver, "MeowOS v%s", g_meowos_version);
ui_draw(W - 8 - ui_width(ver, 20), foot_y, ver, th->outline, 20);
}
}
/* ---- 运行子页(阻塞到返回;返回 0)---- */
int meow_subpage_run(sub_page_t *p)
{
int W = fb_xres, H = fb_yres;
sub_row_t rows[SUB_ROWS_MAX];
int ypos[SUB_ROWS_MAX + 1];
int nrows = 0, scroll_off = 0, total = 0, view_h = 0, scroll_max = 0;
struct keypoll kp_power, kp_keys;
kp_open(&kp_power, DEV_POWER); kp_open(&kp_keys, DEV_KEYS);
struct kd_state kd_vold, kd_volu, kd_play, kd_pwr;
memset(&kd_vold, 0, sizeof kd_vold); memset(&kd_volu, 0, sizeof kd_volu);
memset(&kd_play, 0, sizeof kd_play); memset(&kd_pwr, 0, sizeof kd_pwr);
int m_prev_vold = 0, m_prev_volu = 0, m_prev_play = 0, m_prev_pwr = 0;
for (int w = 0; w < KEYBUF_WORDS; w++) {
if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEDOWN % 32))) m_prev_vold = 1;
if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEUP % 32))) m_prev_volu = 1;
if (kp_keys.cur[w] & (1UL << (KEY_PLAYPAUSE % 32))) m_prev_play = 1;
if (kp_power.cur[w] & (1UL << (KEY_POWER % 32))) m_prev_pwr = 1;
}
int tfd = open(DEV_TOUCH, O_RDONLY | O_NONBLOCK);
if (tfd >= 0) { unsigned char ev[16]; while (read(tfd, ev, sizeof ev) == 16) {} }
unsigned long long touch_cooldown_until = now_ms() + 300;
unsigned long long last_tick = now_ms();
unsigned long long last_act = now_ms();
unsigned long long last_live = now_ms();
int after_modal = 0; /* 子页内模态关闭后:覆盖过状态栏,需整帧重绘还原 */
/* 触摸状态 */
int td = 0, td_ok = 0, tdx = -1, tdy = -1, tlx = -1, tly = -1;
int scroll_drag = 0, scroll_base = 0, scroll_y0 = 0, scroll_invert = 0;
int wake_suppress = 0;
unsigned long long pwr_hold_at = 0;
int pwr_menu_state = 0, pwr_woke = 0, pwr_suppress_click = 0;
/* 顶栏开关初始值 */
int top_on = 0, top_sw_en = 0;
if (p->top_sw) top_sw_en = p->top_sw(p->ctx, &top_on);
/* 首填行 + 首绘 */
nrows = p->rows ? p->rows(p->ctx, rows, SUB_ROWS_MAX) : 0;
if (nrows > SUB_ROWS_MAX) nrows = SUB_ROWS_MAX;
sub_ypos(rows, nrows, ypos);
total = ypos[nrows];
view_h = H - SUB_CONT_TOP;
scroll_max = total > view_h ? total - view_h : 0;
subpage_paint(p, rows, nrows, scroll_off, top_on, top_sw_en);
meow_dirty_full(); meow_ui_flush();
for (;;) {
unsigned long long now = now_ms();
if (sigterm_flag) break;
int need_paint = 0;
/* ---- 自动息屏态:留在子页,等待任意输入唤醒,恢复后重绘(不丢上下文)---- */
if (meow_auto_slept && !meow_screen_on) {
kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL);
int wake = 0;
for (int w = 0; w < KEYBUF_WORDS; w++) {
if (kp_power.cur[w] & (1UL << (KEY_POWER % 32))) wake = 1;
if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEDOWN % 32))) wake = 1;
if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEUP % 32))) wake = 1;
if (kp_keys.cur[w] & (1UL << (KEY_PLAYPAUSE % 32))) wake = 1;
}
if (!wake && screen_off_mode == 1 && tfd >= 0) {
struct pollfd spfd = { tfd, POLLIN, 0 };
if (poll(&spfd, 1, 0) > 0) {
unsigned char ev[16];
while (read(tfd, ev, sizeof ev) == 16) {
uint16_t type = *(uint16_t *)(ev + 8), code = *(uint16_t *)(ev + 10);
int32_t val = *(int32_t *)(ev + 12);
if (type == EV_KEY && code == BTN_TOUCH && val) wake = 1;
}
}
}
if (wake) {
meow_panel_on();
meow_auto_slept = 0;
wake_suppress = 1;
last_act = now;
/* 唤醒:音量基线重播(key_router)+ 本页播放/电源基线 */
key_router_rebase();
memset(&kd_pwr, 0, sizeof kd_pwr); memset(&kd_play, 0, sizeof kd_play);
kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL);
m_prev_pwr = kp_is_down(&kp_power, KEY_POWER);
m_prev_play = kp_is_down(&kp_keys, KEY_PLAYPAUSE);
touch_cooldown_until = now_ms() + 300;
td = 0; td_ok = 0; scroll_drag = 0; tlx = tly = -1;
after_modal = 1;
subpage_paint(p, rows, nrows, scroll_off, top_on, top_sw_en);
meow_dirty_full(); meow_ui_flush();
}
usleep(POLL_MS * 1000);
continue;
}
/* ---- 按键 ---- */
kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL);
int vold = 0, volu = 0, play = 0, pwr = 0;
for (int w = 0; w < KEYBUF_WORDS; w++) {
if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEDOWN % 32))) vold = 1;
if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEUP % 32))) volu = 1;
if (kp_keys.cur[w] & (1UL << (KEY_PLAYPAUSE % 32))) play = 1;
if (kp_power.cur[w] & (1UL << (KEY_POWER % 32))) pwr = 1;
}
(void)kd_tick(&kd_vold, vold); (void)kd_tick(&kd_volu, volu);
(void)kd_tick(&kd_play, play); (void)kd_tick(&kd_pwr, pwr);
vold = kd_vold.stable; volu = kd_volu.stable;
play = kd_play.stable; pwr = kd_pwr.stable;
if (wake_suppress) {
if (!pwr && !vold && !volu && !play) wake_suppress = 0;
} else {
/* 音量键 = 系统音量(对齐设置页,不做光标移动) */
if (vold && !m_prev_vold) { settings_volume_adj(-settings_volume_step); need_paint = 1; }
if (volu && !m_prev_volu) { settings_volume_adj(+settings_volume_step); need_paint = 1; }
if (play && !m_prev_play) { logmsg("subpage: play key caught (media-reserved)"); last_act = now; }
/* 电源键 = 系统级语义(单击息屏/亮屏、长按电源菜单、8s 强关;不映射返回) */
if (pwr && !m_prev_pwr) {
pwr_hold_at = now; pwr_menu_state = 0;
pwr_suppress_click = 0;
last_act = now;
if (meow_auto_slept && !meow_screen_on) pwr_woke = 1;
}
if (pwr && pwr_hold_at) {
if (now - pwr_hold_at >= (unsigned long long)power_forceoff_ms && !pwr_menu_state) {
if (sim_no_exec) { logmsg("subpage pm: [sim-no-exec] POWER HOLD -> force poweroff"); }
else { logmsg("subpage pm: POWER HOLD -> force poweroff"); meow_power_action(POWER_SHUTDOWN); }
} else if (now - pwr_hold_at >= (unsigned long long)power_longpress_menu_ms && !pwr_menu_state) {
pwr_menu_state = 1;
logmsg("subpage pm: POWER HOLD -> show power menu");
show_power_menu();
last_act = now_ms();
pwr_hold_at = 0; pwr_menu_state = 0; pwr_woke = 0;
pwr_suppress_click = 1;
/* 电源菜单返回:音量基线重播(key_router)+ 本页播放/电源基线 */
key_router_rebase();
memset(&kd_pwr, 0, sizeof kd_pwr);
memset(&kd_play, 0, sizeof kd_play);
kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL);
m_prev_pwr = kp_is_down(&kp_power, KEY_POWER);
m_prev_play = kp_is_down(&kp_keys, KEY_PLAYPAUSE);
after_modal = 1;
need_paint = 1;
goto sub_keys_seeded;
}
}
if (!pwr && m_prev_pwr) {
if (pwr_suppress_click) {
logmsg("subpage pm: power release after menu -> no toggle");
} else if (pwr_hold_at && (now - pwr_hold_at < (unsigned long long)power_longpress_menu_ms) &&
!pwr_menu_state && !pwr_woke) {
logmsg("subpage pm: power single-click -> toggle panel");
meow_toggle_panel();
meow_auto_slept = 0;
last_act = now_ms();
need_paint = 1;
} else if (pwr_woke) {
logmsg("subpage pm: power release after auto wake -> no toggle");
}
pwr_hold_at = 0; pwr_menu_state = 0; pwr_woke = 0;
pwr_suppress_click = 0;
}
}
if (vol_ramp_dir != 0) {
int held = (vol_ramp_dir > 0) ? volu : vold;
(void)vol_ramp_tick(held);
g_sys.vol = settings_volume_get();
paint_sb_right();
need_paint = 1;
}
m_prev_vold = vold; m_prev_volu = volu; m_prev_play = play; m_prev_pwr = pwr;
sub_keys_seeded:
;
/* ---- 触摸 ---- */
if (tfd >= 0 && meow_screen_on) {
struct pollfd pfd = { tfd, POLLIN, 0 };
if (poll(&pfd, 1, 0) > 0) {
unsigned char ev[16];
while (read(tfd, ev, sizeof ev) == 16) {
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) tlx = val;
else if (code == ABS_Y || code == ABS_MT_POSITION_Y) tly = val;
if (td && !td_ok && tlx >= 0 && tly >= 0) {
tdx = tlx; tdy = tly; td_ok = 1;
scroll_y0 = tdy;
}
if (td_ok && !scroll_drag) {
int ddy = tly - scroll_y0;
if (ddy >= S_SCROLL_SLOP || ddy <= -S_SCROLL_SLOP) {
scroll_drag = 1;
meow_settings_get_bool("input.swipe_invert", &scroll_invert);
scroll_base = scroll_off + (scroll_invert ? -ddy : ddy);
}
}
if (scroll_drag && td_ok) {
int dydt = tly - scroll_y0;
scroll_off = sub_clamp(scroll_base + (scroll_invert ? dydt : -dydt),
scroll_max);
need_paint = 1;
}
} else if (type == EV_KEY && code == BTN_TOUCH) {
if (val && !td) {
if (now_ms() < touch_cooldown_until) { tlx = tly = -1; }
else { td = 1; td_ok = 0; tlx = -1; tly = -1; scroll_drag = 0; }
} else if (!val && td) {
td = 0;
if (scroll_drag) {
scroll_drag = 0; scroll_base = 0; scroll_y0 = 0;
need_paint = 1;
} else if (td_ok) {
int dx = tlx - tdx, dy = tly - tdy;
/* 左缘左滑返回(复用全局判定) */
if (meow_edge_back(tdx, dx, dy)) {
if (tfd >= 0) { unsigned char e[16]; while (read(tfd, e, sizeof e) == 16) {} }
goto subdone;
}
int adx = dx < 0 ? -dx : dx, ady = dy < 0 ? -dy : dy;
if (adx < 60 && ady < 60) {
int tx = tdx, ty = tdy;
/* 底部返回按钮 */
if (ty >= H - 52 && tx >= SUB_BTN_X && tx <= SUB_BTN_X + SUB_BTN_W) {
if (tfd >= 0) { unsigned char e[16]; while (read(tfd, e, sizeof e) == 16) {} }
goto subdone;
}
/* 顶栏返回箭头 */
if (ty >= SUB_TB_Y && ty < SUB_TB_BOT) {
/* 左上角 48x顶栏高 = 返回箭头 */
if (tx >= 0 && tx < 48) {
if (tfd >= 0) { unsigned char e[16]; while (read(tfd, e, sizeof e) == 16) {} }
goto subdone;
}
/* 顶栏开关 */
if (top_sw_en &&
tx >= W - SUB_PAD_X - SUB_SW_W - 8 && tx < W &&
ty >= SUB_TB_Y + (SUB_TB_H - SUB_SW_H) / 2 - 8 &&
ty < SUB_TB_Y + (SUB_TB_H - SUB_SW_H) / 2 + SUB_SW_H + 8) {
if (p->top_sw_set) p->top_sw_set(p->ctx, !top_on);
top_on = !top_on;
if (p->rows) nrows = p->rows(p->ctx, rows, SUB_ROWS_MAX);
if (nrows > SUB_ROWS_MAX) nrows = SUB_ROWS_MAX;
sub_ypos(rows, nrows, ypos);
total = ypos[nrows];
scroll_max = total > view_h ? total - view_h : 0;
if (scroll_off > scroll_max) scroll_off = scroll_max;
last_act = now;
need_paint = 1;
}
continue;
}
/* 普通行命中 */
int cy = ty - SUB_CONT_TOP + scroll_off;
int idx = -1;
for (int i = 0; i < nrows; i++) {
if (cy >= ypos[i] && cy < ypos[i + 1]) { idx = i; break; }
}
if (idx >= 0) {
sub_kind_t kind = rows[idx].kind;
if (kind == SUB_SW || kind == SUB_ACT) {
if (p->act) p->act(p->ctx, idx, rows[idx].act);
} else if (kind == SUB_ROW) {
if (p->detail) p->detail(p->ctx, idx, rows[idx].act);
}
/* 动作后重扫行:连接/配对/开关已变更数据,列表须反映 */
if (p->rows) nrows = p->rows(p->ctx, rows, SUB_ROWS_MAX);
if (nrows > SUB_ROWS_MAX) nrows = SUB_ROWS_MAX;
sub_ypos(rows, nrows, ypos);
total = ypos[nrows];
scroll_max = total > view_h ? total - view_h : 0;
if (scroll_off > scroll_max) scroll_off = scroll_max;
}
if (idx >= 0) {
/* 回调可能开模态(s_dd_modal 用独立 fd 消费);返回后彻底
* 排空本页 tfd + 重置触摸基线,防穿透(复刻设置页后置排空)。 */
if (tfd >= 0) {
unsigned char e[16];
while (read(tfd, e, sizeof e) == 16) {}
usleep(40000);
while (read(tfd, e, sizeof e) == 16) {}
}
td = 0; td_ok = 0; scroll_drag = 0;
scroll_base = 0; scroll_y0 = 0;
tdx = tdy = tlx = tly = -1;
touch_cooldown_until = now_ms() + 300;
/* 返回重播:音量基线(key_router)+ 本页播放/电源基线 */
key_router_rebase();
memset(&kd_pwr, 0, sizeof kd_pwr);
memset(&kd_play, 0, sizeof kd_play);
kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL);
m_prev_pwr = kp_is_down(&kp_power, KEY_POWER);
m_prev_play = kp_is_down(&kp_keys, KEY_PLAYPAUSE);
after_modal = 1;
need_paint = 1;
}
}
}
td_ok = 0;
}
}
}
}
}
/* ---- 周期性重扫(live 页:蓝牙附近扫描等驻留更新)---- */
if (p->live && now - last_live >= 2000) {
last_live = now;
int new_n = p->rows ? p->rows(p->ctx, rows, SUB_ROWS_MAX) : 0;
if (new_n > SUB_ROWS_MAX) new_n = SUB_ROWS_MAX;
if (new_n != nrows) {
nrows = new_n;
sub_ypos(rows, nrows, ypos);
total = ypos[nrows];
scroll_max = total > view_h ? total - view_h : 0;
if (scroll_off > scroll_max) scroll_off = scroll_max;
last_act = now;
need_paint = 1;
}
}
/* ---- 重绘 ---- */
if (need_paint) {
last_act = now;
subpage_paint(p, rows, nrows, scroll_off, top_on, top_sw_en);
if (after_modal) { meow_dirty_full(); after_modal = 0; }
else meow_dirty_add(0, SUB_TB_Y, fb_xres, fb_yres - SUB_TB_Y); /* 含顶栏(开关/返回/标题) */
meow_ui_flush();
}
/* ---- 自动息屏(空闲超时关屏,留在子页,等输入唤醒)---- */
if (screen_off_timeout > 0 && meow_screen_on && !meow_auto_slept &&
(unsigned long long)(now - last_act) >= (unsigned long long)screen_off_timeout * 1000) {
meow_panel_off();
meow_auto_slept = 1;
wake_suppress = 1;
logmsg("subpage: idle %ds -> auto screen-off (stay)", screen_off_timeout);
}
/* ~1s 状态栏时钟刷新 */
if (now - last_tick >= 1000) {
last_tick = now;
draw_clock();
fb_norm_buf0();
meow_ui_flush();
}
usleep(POLL_MS * 1000);
}
subdone:
if (tfd >= 0) close(tfd);
kp_close(&kp_power); kp_close(&kp_keys);
logmsg("subpage '%s' closed", p->title ? p->title : "");
return 0;
}
#endif /* M7.1a 退役:旧手绘子页脚手架回退位 */
/* ==================================================================
* M7.1a 子页渲染层迁移(SDK 控件化):
* - 顶栏 → meow_topappbar(标题 + 返回箭头 + 底部分隔线);
* - 顶部开关 → meow_switch(叠顶栏右缘,保留开/关视觉 + 命中);
* - 分组列表 → meow_listview(virtualized set_vsource 固定 item 池 + section 段头
* + right=SWITCH/ARROW 尾件 + 选中/按压态,SDK 语义色);
* - 滚动 → meow_scroll(跟手 meow_scroll_track 采样 + 松手 fling ease-out 惯性停;
* 刻意不 set_snap——SDK snap 以 fling 起点为吸附目标会往回拉,见 sp_widgets_create 注)。
* 控件 lazy 创建一次(owner=SUB_SENTINEL_ACT 哨兵),跨多次进入复用;meow_subpage_run()
* 进入 push 哨兵、退出 pop(owner 闸门使控件命中生效)。数据/动作回调(rows/act/detail/
* top_sw_*)仍由各子页提供,逻辑层不动,仅渲染层替换。
* ================================================================== */
#define SUB_SENTINEL_ACT 0x52 /* 子页哨兵 activity(不与 launcher 其它页冲突) */
static sub_page_t *g_sp; /* 当前运行子页(rows/act/detail/top_sw 回调入口) */
static sub_row_t g_rows[SUB_ROWS_MAX]; /* 行缓冲:rows() 一次性填满,listview bind 只读 */
static int g_nrows = 0; /* 当前行数(listview vsource count 实时读) */
static int g_top_sw_en = 0; /* 本页是否显示顶开关 */
static int g_top_on = 0; /* 顶开关当前值 */
static meow_widget *g_tb = NULL; /* topappbar */
static meow_widget *g_sw = NULL; /* 顶开关(仅在有顶开关的页创建/使用) */
static meow_widget *g_lv = NULL; /* listview */
static meow_scroll *g_sc = NULL; /* 滚动物理 */
static int g_sp_init = 0;
/* 列表视口矩形(内容区,去掉底部版本带) */
static void sp_lv_rect(int *x, int *y, int *w, int *h)
{
if (x) *x = 0;
if (y) *y = SUB_CONT_TOP;
if (w) *w = fb_xres;
if (h) { *h = fb_yres - SUB_CONT_TOP - SUB_FOOT_H; if (*h < 0) *h = 0; }
}
/* ---- 行缓冲 → meow_lv_item(虚拟化 bind;每行轻量填充,不做 IO §5.5)---- */
static int sp_lv_count(void *ctx) { (void)ctx; return g_nrows; }
static void sp_lv_bind(int idx, meow_lv_item *out, void *ctx)
{
(void)ctx;
memset(out, 0, sizeof *out);
out->icon_kind = MEOW_LIST_ICON_NONE;
out->enabled = 1;
if (idx < 0 || idx >= g_nrows) { out->title = ""; return; }
const sub_row_t *r = &g_rows[idx];
switch (r->kind) {
case SUB_HDR: /* 段头占位行(非粘性):default renderer 特殊绘制 */
out->section = 1; out->title = r->label ? r->label : "";
out->right = MEOW_LIST_RIGHT_NONE; out->enabled = 0; break;
case SUB_ROW: /* 普通信息行 */
out->title = r->label ? r->label : "";
out->subtitle = r->sub; out->right = MEOW_LIST_RIGHT_NONE; break;
case SUB_SW: /* 开关行:右尾件 SWITCH */
out->title = r->label ? r->label : "";
out->subtitle = r->sub; out->right = MEOW_LIST_RIGHT_SWITCH; out->on = r->on; break;
case SUB_ACT: /* 动作行:右尾件 ARROW */
out->title = r->label ? r->label : "";
out->subtitle = r->sub; out->right = MEOW_LIST_RIGHT_ARROW; break;
}
}
/* ---- 行完全自绘(批③:页面经 sub_page_t.row_draw 定制整行;默认 renderer 被覆盖)---- */
static void sp_lv_draw(int idx, int x, int y, int w, int h, int state, void *ctx)
{
(void)ctx;
if (!g_sp || !g_sp->row_draw) return;
if (idx < 0 || idx >= g_nrows) return;
g_sp->row_draw(g_sp->ctx, idx, &g_rows[idx], x, y, w, h, state);
}
/* ---- 顶栏标题栏右侧动作按钮矩形(批③:sub_page_t.top_action)---- */
static void sp_top_action_rect(int *x, int *y, int *w, int *h)
{
if (x) *x = 0;
if (y) *y = 0;
if (w) *w = 0;
if (h) *h = 0;
if (!g_sp || !g_sp->top_action) return;
int tw = ui_width(g_sp->top_action, 20);
if (tw <= 0) return;
int bw = tw + 24, bh = 40; /* 含内边距的圆角按钮 */
*x = fb_xres - 12 - bw;
*y = SUB_TB_Y + (SUB_TB_H - bh) / 2;
*w = bw; *h = bh;
}
/* 绘制顶栏动作按钮(sp_paint_page 内调用)。 */
static void sp_paint_top_action(void)
{
if (!g_sp || !g_sp->top_action) return;
int x, y, w, h; sp_top_action_rect(&x, &y, &w, &h);
if (w <= 0 || h <= 0) return;
const meow_theme *mt = meow_theme_get();
uint32_t col = mt->primary;
meow_ui_draw_rect_round(x, y, w, h, h / 2, col); /* 全形=胶囊描边按钮(P1) */
int th = meow_ui_text_height(20);
if (th > h) th = h;
meow_ui_text_center(x + w / 2, y + (h - th) / 2, g_sp->top_action, 20, col);
}
/* ---- meow_scroll 回调(包 listview)---- */
static void sp_sc_set(void *ctx, int off) { (void)ctx; if (g_lv) meow_listview_set_scroll(g_lv, off); }
static int sp_sc_max(void *ctx) { (void)ctx; return g_lv ? meow_listview_max_scroll(g_lv) : 0; }
static int sp_sc_get(void *ctx) { (void)ctx; return g_lv ? meow_listview_scroll(g_lv) : 0; }
/* 列表区重绘(含背景 + listview),供 fling 每帧/滚动局部刷。ctx 为 meow_scroll 回调用。 */
static void sp_redraw_region(void *ctx)
{
(void)ctx;
int lvx, lvy, lvw, lvh;
sp_lv_rect(&lvx, &lvy, &lvw, &lvh);
if (lvh <= 0) return;
const meow_theme *mt = meow_theme_get();
meow_ui_fill_rect(lvx, lvy, lvw, lvh, mt->surface); /* 内容底 surface */
if (g_lv) meow_widget_draw(g_lv);
meow_dirty_add(lvx, lvy, lvw, lvh);
meow_ui_flush();
}
/* 创建子页控件集(owner=SUB_SENTINEL_ACT;仅首进创建一次,跨页复用)。 */
static void sp_widgets_create(void)
{
meow_ui_activity_push(SUB_SENTINEL_ACT);
g_tb = meow_topappbar_create(0, SUB_TB_Y, fb_xres, SUB_TB_H, "");
if (g_tb) meow_topappbar_set_back(g_tb, 1);
int lvx, lvy, lvw, lvh;
sp_lv_rect(&lvx, &lvy, &lvw, &lvh);
g_lv = meow_listview_create(lvx, lvy, lvw, lvh, 0, NULL, NULL, MEOW_LIST_SEL_NONE);
if (g_lv) {
meow_listview_set_vsource(g_lv, sp_lv_count, sp_lv_bind, NULL);
/* 双行富 item + 无左图标 + 右尾件(SWITCH/ARROW);行高 56 */
meow_listview_set_item_renderer(g_lv, 2, 0, 1);
meow_listview_set_item_h(g_lv, SUB_ITEM_H);
/* MD3(P0):默认 renderer 容器模式——行=surfaceContainer 圆角卡+行间空隙,
* 选中/按压 tonal(primaryContainer/primary α26)、去冗余分隔线;段头=surface 上的 primary 段标签。 */
meow_listview_set_container(g_lv, 1);
}
/* 注:刻意不 set_snap —— SDK meow_scroll 的 snap 以「fling 起点」为吸附目标,快速滑动时会
* 从自然停点往回拉一整行(实测 697→336),UX 不佳;默认 ease-out 惯性停(无 snap)更顺滑。
* 若后续 SDK 修正 snap 到「自然停点」,可恢复 meow_scroll_set_snap(g_sc, SUB_ITEM_H)。 */
g_sc = meow_scroll_create(sp_sc_set, sp_sc_max, sp_sc_get, sp_redraw_region, NULL);
meow_ui_activity_pop();
g_sp_init = 1;
}
/* 逐页配置:topappbar 标题 + 顶开关(按需创建/置态)+ listview 行数。 */
static void sp_configure_page(const sub_page_t *p, int top_sw_en, int top_on)
{
if (p->title && g_tb) meow_topappbar_set_title(g_tb, p->title);
g_top_sw_en = top_sw_en;
g_top_on = top_on;
if (top_sw_en) {
if (!g_sw) {
meow_ui_activity_push(SUB_SENTINEL_ACT); /* owner 钉哨兵(命中闸门) */
int swx = fb_xres - SUB_PAD_X - 56; /* meow_switch 固定 56×32 */
int swy = SUB_TB_Y + (SUB_TB_H - SUB_SW_H) / 2;
g_sw = meow_switch_create(swx, swy, top_on);
meow_ui_activity_pop();
} else if (g_sw) {
meow_switch_set(g_sw, top_on);
}
}
/* 批③:行完全自绘 on/off —— 页提供 row_draw 用自绘;否则清 NULL 回默认 renderer。 */
if (g_lv) meow_listview_set_item_draw(g_lv, p->row_draw ? sp_lv_draw : NULL, NULL);
}
/* 整页绘制(进 back_buf;调用方 meow_dirty_full + flush 上屏)。 */
static void sp_paint_page(void)
{
const meow_theme *mt = meow_theme_get();
int W = fb_xres, H = fb_yres;
meow_ui_fill_rect(0, 0, W, H, mt->surface); /* 全屏底 surface */
draw_statusbar();
if (g_tb) meow_widget_draw(g_tb); /* topappbar(含底部分隔线) */
if (g_sw && g_top_sw_en) meow_widget_draw(g_sw); /* 顶开关(叠顶栏右缘) */
sp_paint_top_action(); /* 批③:顶栏右侧动作按钮 */
int lvx, lvy, lvw, lvh;
sp_lv_rect(&lvx, &lvy, &lvw, &lvh);
meow_ui_fill_rect(lvx, lvy, lvw, lvh, mt->surface); /* 内容底 surface */
if (g_lv) meow_widget_draw(g_lv); /* 分组列表 */
/* 底部版本信息(右对齐 dim) */
char ver[32];
snprintf(ver, sizeof ver, "MeowOS v%s", g_meowos_version);
ui_draw(W - 8 - ui_width(ver, 18), H - SUB_FOOT_H + 10, ver,
s_ui_theme()->outline, 18);
}
/* 顶部开关切换:写设置 + 生效 + 触发重扫 + 重填行。 */
static void sp_top_sw_toggle(void)
{
if (!g_sp || !g_sp->top_sw_set) return;
g_sp->top_sw_set(g_sp->ctx, !g_top_on);
g_top_on = !g_top_on;
if (g_sw) meow_switch_set(g_sw, g_top_on);
if (g_sp->rows) {
g_nrows = g_sp->rows(g_sp->ctx, g_rows, SUB_ROWS_MAX);
if (g_nrows > SUB_ROWS_MAX) g_nrows = SUB_ROWS_MAX;
}
}
/* 动作后重扫行(act/detail 已变更数据):重填行并 clamp 滚动。 */
static void sp_refresh_rows(void)
{
if (g_sp && g_sp->rows) {
g_nrows = g_sp->rows(g_sp->ctx, g_rows, SUB_ROWS_MAX);
if (g_nrows > SUB_ROWS_MAX) g_nrows = SUB_ROWS_MAX;
}
if (g_lv) {
int ms = meow_listview_max_scroll(g_lv);
if (meow_listview_scroll(g_lv) > ms) meow_listview_set_scroll(g_lv, ms);
}
}
/* ---- 运行子页(阻塞到返回;返回 0)---- */
int meow_subpage_run(sub_page_t *p)
{
struct keypoll kp_power, kp_keys;
kp_open(&kp_power, DEV_POWER); kp_open(&kp_keys, DEV_KEYS);
/* 音量键防抖/基线已上收 key_router(进程唯一)——本页只保留播放/电源键自家防抖。 */
struct kd_state kd_play, kd_pwr;
memset(&kd_play, 0, sizeof kd_play); memset(&kd_pwr, 0, sizeof kd_pwr);
int m_prev_play = 0, m_prev_pwr = 0;
for (int w = 0; w < KEYBUF_WORDS; w++) {
if (kp_keys.cur[w] & (1UL << (KEY_PLAYPAUSE % 32))) m_prev_play = 1;
if (kp_power.cur[w] & (1UL << (KEY_POWER % 32))) m_prev_pwr = 1;
}
key_router_rebase(); /* 进入子页:音量基线对齐当前 raw(防残留边沿) */
int tfd = open(DEV_TOUCH, O_RDONLY | O_NONBLOCK);
if (tfd >= 0) { unsigned char ev[16]; while (read(tfd, ev, sizeof ev) == 16) {} }
unsigned long long touch_cooldown_until = now_ms() + 300;
unsigned long long last_tick = now_ms();
unsigned long long last_act = now_ms();
unsigned long long last_live = now_ms();
int after_modal = 0; /* 子页内模态关闭后:覆盖过状态栏,需整帧重绘还原 */
/* 触摸状态 */
int td = 0, td_ok = 0, tdx = -1, tdy = -1, tlx = -1, tly = -1;
int scroll_drag = 0, scroll_invert = 0, drag_last_y = 0;
int wake_suppress = 0;
unsigned long long pwr_hold_at = 0;
int pwr_menu_state = 0, pwr_woke = 0, pwr_suppress_click = 0;
/* 顶栏开关初始值 */
int top_on = 0, top_sw_en = 0;
if (p->top_sw) top_sw_en = p->top_sw(p->ctx, &top_on);
/* 首填行 + lazy 建控件 + 配置 + 首绘 */
g_sp = p;
g_nrows = p->rows ? p->rows(p->ctx, g_rows, SUB_ROWS_MAX) : 0;
if (g_nrows > SUB_ROWS_MAX) g_nrows = SUB_ROWS_MAX;
if (!g_sp_init) sp_widgets_create();
sp_configure_page(p, top_sw_en, top_on);
meow_ui_activity_push(SUB_SENTINEL_ACT); /* 控件 owner 闸门打开(hit 生效) */
key_router_page_enter(SUB_SENTINEL_ACT); /* 路由登记活动页(与 activity 栈交叉校验) */
sp_paint_page();
meow_dirty_full(); meow_ui_flush();
for (;;) {
unsigned long long now = now_ms();
if (sigterm_flag) break;
int need_paint = 0;
/* ---- 自动息屏态:留在子页,等待任意输入唤醒,恢复后重绘(不丢上下文)---- */
if (meow_auto_slept && !meow_screen_on) {
kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL);
int wake = 0;
for (int w = 0; w < KEYBUF_WORDS; w++) {
if (kp_power.cur[w] & (1UL << (KEY_POWER % 32))) wake = 1;
if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEDOWN % 32))) wake = 1;
if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEUP % 32))) wake = 1;
if (kp_keys.cur[w] & (1UL << (KEY_PLAYPAUSE % 32))) wake = 1;
}
if (!wake && screen_off_mode == 1 && tfd >= 0) {
struct pollfd spfd = { tfd, POLLIN, 0 };
if (poll(&spfd, 1, 0) > 0) {
unsigned char ev[16];
while (read(tfd, ev, sizeof ev) == 16) {
uint16_t type = *(uint16_t *)(ev + 8), code = *(uint16_t *)(ev + 10);
int32_t val = *(int32_t *)(ev + 12);
if (type == EV_KEY && code == BTN_TOUCH && val) wake = 1;
}
}
}
if (wake) {
meow_panel_on();
meow_auto_slept = 0;
wake_suppress = 1;
last_act = now;
/* 唤醒:音量基线重播(key_router)+ 本页播放/电源基线 */
key_router_rebase();
memset(&kd_pwr, 0, sizeof kd_pwr); memset(&kd_play, 0, sizeof kd_play);
kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL);
m_prev_pwr = kp_is_down(&kp_power, KEY_POWER);
m_prev_play = kp_is_down(&kp_keys, KEY_PLAYPAUSE);
touch_cooldown_until = now_ms() + 300;
td = 0; td_ok = 0; scroll_drag = 0; drag_last_y = 0; tlx = tly = -1;
after_modal = 1;
sp_paint_page();
meow_dirty_full(); meow_ui_flush();
}
usleep(POLL_MS * 1000);
continue;
}
/* ---- 按键(与旧版逐字一致:音量=系统音量/播放=媒体预留/电源=系统级语义)---- */
key_evt_t ev = key_router_pump(); /* 音量边沿/held/ramp(进程唯一防抖基线) */
kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL);
int play = 0, pwr = 0;
for (int w = 0; w < KEYBUF_WORDS; w++) {
if (kp_keys.cur[w] & (1UL << (KEY_PLAYPAUSE % 32))) play = 1;
if (kp_power.cur[w] & (1UL << (KEY_POWER % 32))) pwr = 1;
}
(void)kd_tick(&kd_play, play); (void)kd_tick(&kd_pwr, pwr);
play = kd_play.stable; pwr = kd_pwr.stable;
if (wake_suppress) {
if (!pwr && !ev.vol_st_dn && !ev.vol_st_up && !play) wake_suppress = 0;
} else {
if (ev.vol_dn_edge) { settings_volume_adj(-settings_volume_step); need_paint = 1; }
if (ev.vol_up_edge) { settings_volume_adj(+settings_volume_step); need_paint = 1; }
if (play && !m_prev_play) { logmsg("subpage: play key caught (media-reserved)"); last_act = now; }
if (pwr && !m_prev_pwr) {
pwr_hold_at = now; pwr_menu_state = 0;
pwr_suppress_click = 0;
last_act = now;
if (meow_auto_slept && !meow_screen_on) pwr_woke = 1;
}
if (pwr && pwr_hold_at) {
if (now - pwr_hold_at >= (unsigned long long)power_forceoff_ms && !pwr_menu_state) {
if (sim_no_exec) { logmsg("subpage pm: [sim-no-exec] POWER HOLD -> force poweroff"); }
else { logmsg("subpage pm: POWER HOLD -> force poweroff"); meow_power_action(POWER_SHUTDOWN); }
} else if (now - pwr_hold_at >= (unsigned long long)power_longpress_menu_ms && !pwr_menu_state) {
pwr_menu_state = 1;
logmsg("subpage pm: POWER HOLD -> show power menu");
show_power_menu();
last_act = now_ms();
pwr_hold_at = 0; pwr_menu_state = 0; pwr_woke = 0;
pwr_suppress_click = 1;
/* 电源菜单返回:音量基线重播(key_router)+ 本页播放/电源基线 */
key_router_rebase();
memset(&kd_pwr, 0, sizeof kd_pwr);
memset(&kd_play, 0, sizeof kd_play);
kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL);
m_prev_pwr = kp_is_down(&kp_power, KEY_POWER);
m_prev_play = kp_is_down(&kp_keys, KEY_PLAYPAUSE);
after_modal = 1;
need_paint = 1;
goto sub_keys_seeded;
}
}
if (!pwr && m_prev_pwr) {
if (pwr_suppress_click) {
logmsg("subpage pm: power release after menu -> no toggle");
} else if (pwr_hold_at && (now - pwr_hold_at < (unsigned long long)power_longpress_menu_ms) &&
!pwr_menu_state && !pwr_woke) {
logmsg("subpage pm: power single-click -> toggle panel");
meow_toggle_panel();
meow_auto_slept = 0;
last_act = now_ms();
need_paint = 1;
} else if (pwr_woke) {
logmsg("subpage pm: power release after auto wake -> no toggle");
}
pwr_hold_at = 0; pwr_menu_state = 0; pwr_woke = 0;
pwr_suppress_click = 0;
}
}
if (vol_ramp_dir != 0) {
int held = (vol_ramp_dir > 0) ? ev.vol_held_up : ev.vol_held_dn;
(void)vol_ramp_tick(held);
g_sys.vol = settings_volume_get();
paint_sb_right();
need_paint = 1;
}
m_prev_play = play; m_prev_pwr = pwr;
sub_keys_seeded:
;
/* ---- 触摸(SDK 控件命中:topappbar/顶开关/listview + meow_scroll 滚动)---- */
if (tfd >= 0 && meow_screen_on) {
struct pollfd pfd = { tfd, POLLIN, 0 };
if (poll(&pfd, 1, 0) > 0) {
unsigned char ev[16];
while (read(tfd, ev, sizeof ev) == 16) {
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) tlx = val;
else if (code == ABS_Y || code == ABS_MT_POSITION_Y) tly = val;
if (td && !td_ok && tlx >= 0 && tly >= 0) {
tdx = tlx; tdy = tly; td_ok = 1;
meow_input_touch_set(1, tdx, tdy, 0); /* 喂 SDK 输入态(按压态渲染) */
drag_last_y = tdy;
if (g_lv && meow_widget_hit(g_lv, tlx, tly)) /* 按压态即时反馈 */
sp_redraw_region(NULL);
}
if (td_ok && !scroll_drag) {
int ddy = tly - drag_last_y;
if (ddy >= S_SCROLL_SLOP || ddy <= -S_SCROLL_SLOP) {
scroll_drag = 1;
meow_settings_get_bool("input.swipe_invert", &scroll_invert);
meow_input_touch_set(0, 0, 0, 0); /* 滚动中不压行(清按压态) */
drag_last_y = tly; /* 从此增量(跨 slop 无跳变,对齐旧版补偿) */
}
}
if (scroll_drag && td_ok) {
int dydt = tly - drag_last_y; /* 本次位移增量(跟手) */
drag_last_y = tly;
int scdy = scroll_invert ? dydt : -dydt;
if (g_sc && scdy) meow_scroll_track(g_sc, scdy, now);
sp_redraw_region(NULL); /* 列表区重绘(新 offset) */
need_paint = 1;
}
} else if (type == EV_KEY && code == BTN_TOUCH) {
if (val && !td) {
if (now_ms() < touch_cooldown_until) { tlx = tly = -1; }
else { td = 1; td_ok = 0; tlx = -1; tly = -1; scroll_drag = 0;
meow_input_touch_set(0, 0, 0, 0); }
} else if (!val && td) {
td = 0;
meow_input_touch_set(0, 0, 0, 0); /* 抬手:清 SDK 按压态 */
if (scroll_drag) {
scroll_drag = 0;
if (g_sc) meow_scroll_fling(g_sc); /* 松手 fling(速度采样) */
} else if (td_ok) {
int dx = tlx - tdx, dyy = tly - tdy;
/* 左缘左滑返回(全局手势语义) */
if (meow_edge_back(tdx, dx, dyy)) {
if (tfd >= 0) { unsigned char e[16]; while (read(tfd, e, sizeof e) == 16) {} }
goto subdone;
}
int adx = dx < 0 ? -dx : dx, ady = dyy < 0 ? -dyy : dyy;
if (adx < 60 && ady < 60) {
int tx = tlx, ty = tly;
/* 顶栏:返回箭头(topappbar hit)/ 顶开关(meow_switch hit) */
int tb_hit = g_tb ? meow_topappbar_hit(g_tb, tx, ty) : -1;
if (tb_hit == MEOW_TOPBAR_BACK) {
if (tfd >= 0) { unsigned char e[16]; while (read(tfd, e, sizeof e) == 16) {} }
goto subdone;
}
/* 批③:顶栏标题栏右侧动作按钮(一键清除;先于顶开关判定,二者页间互斥) */
if (g_sp && g_sp->top_action) {
int ax, ay, aw, ah; sp_top_action_rect(&ax, &ay, &aw, &ah);
if (aw > 0 && tx >= ax && tx < ax + aw && ty >= ay && ty < ay + ah) {
if (g_sp->top_action_cb) g_sp->top_action_cb(g_sp->ctx);
last_act = now;
sp_refresh_rows(); /* 清后立即重扫行 */
need_paint = 1;
if (tfd >= 0) { unsigned char e[16]; while (read(tfd, e, sizeof e) == 16) {} }
continue;
}
}
if (g_top_sw_en && g_sw && meow_widget_hit(g_sw, tx, ty)) {
sp_top_sw_toggle();
last_act = now;
need_paint = 1;
if (tfd >= 0) { unsigned char e[16]; while (read(tfd, e, sizeof e) == 16) {} }
continue;
}
/* 列表行命中(SDK 命中:含滚动换算 + 越界/禁用行不响应) */
int row_hit = g_lv ? meow_listview_hit(g_lv, tx, ty) : -1;
if (row_hit >= 0) {
sub_kind_t kind = g_rows[row_hit].kind;
if (kind == SUB_ROW && g_sp->row_tap) {
/* 批③:行内自定义命中(行内杀按钮 vs 行体;页自判,返回 1=重绘) */
int rry = SUB_CONT_TOP + row_hit * SUB_ITEM_H -
(g_lv ? meow_listview_scroll(g_lv) : 0);
g_sp->row_tap(g_sp->ctx, row_hit, &g_rows[row_hit],
tx, ty, 0, rry, fb_xres, SUB_ITEM_H);
} else if (kind == SUB_SW || kind == SUB_ACT) {
if (g_sp->act) g_sp->act(g_sp->ctx, row_hit, g_rows[row_hit].act);
} else if (kind == SUB_ROW) {
if (g_sp->detail) g_sp->detail(g_sp->ctx, row_hit, g_rows[row_hit].act);
}
/* 动作后重扫行 + clamp 滚动 */
sp_refresh_rows();
}
/* 仅命中行(回调可能开模态)才做后置排空/触摸基线重置/整帧重绘
* (对齐旧版 idx>=0 分支;空白区点按不重绘不排空,置 td_ok=0 即可)。 */
if (row_hit >= 0) {
if (tfd >= 0) {
unsigned char e[16];
while (read(tfd, e, sizeof e) == 16) {}
usleep(40000);
while (read(tfd, e, sizeof e) == 16) {}
}
td = 0; td_ok = 0; scroll_drag = 0;
drag_last_y = 0;
tdx = tdy = tlx = tly = -1;
touch_cooldown_until = now_ms() + 300;
/* 返回重播:音量基线(key_router)+ 本页播放/电源基线 */
key_router_rebase();
memset(&kd_pwr, 0, sizeof kd_pwr);
memset(&kd_play, 0, sizeof kd_play);
kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL);
m_prev_pwr = kp_is_down(&kp_power, KEY_POWER);
m_prev_play = kp_is_down(&kp_keys, KEY_PLAYPAUSE);
after_modal = 1;
need_paint = 1;
}
}
}
td_ok = 0;
}
}
}
}
}
/* ---- 周期性重扫(live 页:蓝牙附近扫描等驻留更新)---- */
if (g_sp && g_sp->live && now - last_live >= 2000) {
last_live = now;
int new_n = g_sp->rows ? g_sp->rows(g_sp->ctx, g_rows, SUB_ROWS_MAX) : 0;
if (new_n > SUB_ROWS_MAX) new_n = SUB_ROWS_MAX;
if (new_n != g_nrows) {
g_nrows = new_n;
int ms = g_lv ? meow_listview_max_scroll(g_lv) : 0;
if (g_lv && meow_listview_scroll(g_lv) > ms) meow_listview_set_scroll(g_lv, ms);
last_act = now;
need_paint = 1;
}
}
/* ---- 重绘(整帧/内容区局部;fling 由 meow_scroll redraw 回调自刷)---- */
if (need_paint) {
last_act = now;
sp_paint_page();
if (after_modal) { meow_dirty_full(); after_modal = 0; }
else meow_dirty_add(0, SUB_TB_Y, fb_xres, fb_yres - SUB_TB_Y); /* 含顶栏(开关/返回/标题) */
meow_ui_flush();
}
/* ---- 自动息屏(空闲超时关屏,留在子页,等输入唤醒)---- */
if (screen_off_timeout > 0 && meow_screen_on && !meow_auto_slept &&
(unsigned long long)(now - last_act) >= (unsigned long long)screen_off_timeout * 1000) {
meow_panel_off();
meow_auto_slept = 1;
wake_suppress = 1;
logmsg("subpage: idle %ds -> auto screen-off (stay)", screen_off_timeout);
}
/* ~1s 状态栏时钟刷新 */
if (now - last_tick >= 1000) {
last_tick = now;
draw_clock();
fb_norm_buf0();
meow_ui_flush();
}
/* M7.1a: 推进 fling/回弹/snap 动画(无动画零开销) */
meow_anim_tick();
usleep(POLL_MS * 1000);
}
subdone:
meow_input_touch_set(0, 0, 0, 0); /* 防残留按压态 */
meow_ui_activity_pop(); /* 关 owner 闸门(控件 owner 仍钉哨兵,复用) */
key_router_page_exit(SUB_SENTINEL_ACT); /* 路由:退出登记 + 重播音量基线 */
if (tfd >= 0) close(tfd);
kp_close(&kp_power); kp_close(&kp_keys);
logmsg("subpage '%s' closed", p->title ? p->title : "");
return 0;
}