Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/subpage.c
T
AskaEth 3d70a3dd2c feat: M6.10–M6.19 批量收编 + M6.19 推卡验收
- M7 launcher 重写 5 期(虚拟化宫格/meow_theme 单一权威/横幅动画/自定义 PIN 键盘)
- OOM 通用化(cachegov 上收 SDK)、自动切歌根治、meowpanel jv_free 泄漏修复、Hello 滚动卡顿修复
- ncm 歌词 UI / 媒体通知封面 / 封面分辨率 640
- 5 bug 修复:ncm 后台化 UI 叠层+点击穿透、再进 ncm 桌面叠层、播放页新旧 UI 叠层抢刷脏区(meow_ui_gfx 恢复边沿整帧重画)、PIN 亮屏亮度跳(keys.c meow_panel_on 按 settings_brightness_clamped 恢复)、通知中心封面显歌单封面(cover.c save_cover_raw 加 is_song + 命中块守卫)
- 四 App 统一重链新 meow_ui.a 51caee4a;M6.19=0.8.19 固件 2d2331f2 推卡自刷验收全绿
2026-09-03 12:16:01 +08:00

1035 lines
54 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;
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;
}
}
/* ---- 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);
}
/* 注:刻意不 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);
}
}
}
/* 整页绘制(进 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); /* 顶开关(叠顶栏右缘) */
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_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_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;
}