- 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 推卡自刷验收全绿
1035 lines
54 KiB
C
1035 lines
54 KiB
C
/* 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;
|
||
}
|