/* 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; memset(&kd_pwr, 0, sizeof kd_pwr); memset(&kd_vold, 0, sizeof kd_vold); memset(&kd_volu, 0, sizeof kd_volu); 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_vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN); m_prev_volu = kp_is_down(&kp_keys, KEY_VOLUMEUP); 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; memset(&kd_pwr, 0, sizeof kd_pwr); memset(&kd_vold, 0, sizeof kd_vold); memset(&kd_volu, 0, sizeof kd_volu); 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_vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN); m_prev_volu = kp_is_down(&kp_keys, KEY_VOLUMEUP); 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; memset(&kd_pwr, 0, sizeof kd_pwr); memset(&kd_vold, 0, sizeof kd_vold); memset(&kd_volu, 0, sizeof kd_volu); 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_vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN); m_prev_volu = kp_is_down(&kp_keys, KEY_VOLUMEUP); 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); 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_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 生效) */ 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; memset(&kd_pwr, 0, sizeof kd_pwr); memset(&kd_vold, 0, sizeof kd_vold); memset(&kd_volu, 0, sizeof kd_volu); 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_vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN); m_prev_volu = kp_is_down(&kp_keys, KEY_VOLUMEUP); 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; } /* ---- 按键(与旧版逐字一致:音量=系统音量/播放=媒体预留/电源=系统级语义)---- */ 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; } 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; memset(&kd_pwr, 0, sizeof kd_pwr); memset(&kd_vold, 0, sizeof kd_vold); memset(&kd_volu, 0, sizeof kd_volu); 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_vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN); m_prev_volu = kp_is_down(&kp_keys, KEY_VOLUMEUP); 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: ; /* ---- 触摸(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; memset(&kd_pwr, 0, sizeof kd_pwr); memset(&kd_vold, 0, sizeof kd_vold); memset(&kd_volu, 0, sizeof kd_volu); 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_vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN); m_prev_volu = kp_is_down(&kp_keys, KEY_VOLUMEUP); 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 仍钉哨兵,复用) */ if (tfd >= 0) close(tfd); kp_close(&kp_power); kp_close(&kp_keys); logmsg("subpage '%s' closed", p->title ? p->title : ""); return 0; }