/* subpage.c — MeowOS launcher 独立子页面(WiFi/蓝牙)统一脚手架(M2.76)。 * * 安卓风子页骨架:顶栏(标题 + 返回 + 右上开关)+ 分组列表(组标题/行/开关/动作) * + 滚动(跟手,方向沿用设置页 input.swipe_invert 语义)+ 左缘左滑返回 + 底部返回。 * * 由页面(wifi_page.c / bt_page.c)通过 sub_page_t 提供: * - title 顶栏标题 * - top_sw 顶栏开关(0=不显示;1=显示并回写当前值) * - top_sw_set 开关切换(写设置 + 生效 + 触发重扫) * - rows 填行(组标题/普通行/开关行/动作行),返回行数 * - act 点动作/开关行 * - detail 点普通行(如当前连接详情、网络/设备详情) * - live 1=驻留时周期性(约2s)重扫行(蓝牙附近扫描等动态数据) * * 与设置页同主题(s_ui_theme,MD3 语义色,明暗自动);局部刷新不整屏重绘; * 模态弹窗交给 launcher 既有 s_dd_modal(点外关闭不穿透),本脚手架在回调返回后 * 彻底排空本页 tfd + 重置触摸基线(复刻 run_settings_page 后置排空,防穿透)。 * * 物理键:电源=系统级语义(单击息屏/亮屏、长按电源菜单、8s 强关)、音量=系统音量、 * 播放=抓住——不再映射为返回(APPS §3.4.1 全局接管)。 */ #include "meow_launcher_internal.h" /* ---- 布局常量(顶栏/内容区;全屏内容区延用设置页 surface_variant 整屏底)---- */ #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 + 6) /* 102:内容区顶缘 */ #define SUB_ROW_H 58 /* 行高(对齐 SROW_H,便于触摸) */ #define SUB_HDR_H 40 /* 组标题行高 */ #define SUB_PAD_X 16 /* 水平内边距 */ #define SUB_SW_W 48 /* 开关宽(s_draw_switch) */ #define SUB_SW_H 28 #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; }