Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/power_menu.c
T
AskaEth 63b278e13e fix(watchdog+launcher+sdk): M40 电源键 time64 ABI + 锁屏/叠加残留链
- sdk/meow_time32: 抽出可独立链接的 o32 time32 取时/睡眠设施(不依赖 meow_ui.a,
  watchdog 等独立静态组件可直接链;可选 -DMEOW_TIME32_LIBC_OVERRIDES 接管 libc 入口)
- watchdog: now_ms/usleep 改走 time32(修复 held 变天文数字→每次单击都弹菜单);
  阈值读 power.longpress_menu_ms/forceoff_ms(缺省 3000/8000),删除
  PWR_TOGGLE_MS==PWR_MENU_MS;新增 wd_power.h 纯判定 + 宿主行为矩阵测试
- launcher: App 前台电源菜单补齐 §10.5(notif 全抑制 + 帧恢复 + SIGCONT 排空 +
  标记翻回前台);show_power_menu 恢复进入前背景帧(关闭后不再停留菜单画面);
  重绘挪到首轮按键采样之后;activity 标记排空窗 + 会话入口清理 + manual_off 去闩锁
- lock_screen: 电源改按下边沿 + 进场 300ms 才熄屏;电源熄屏退出也恢复底层帧;
  launcher 在 SIGCONT 前写 /tmp/meow_app_repaint,SDK present 消费并 dirty_full
- power_menu: 菜单内强关接 power.forceoff_ms(按 longpress 抵扣),日志不再谎称 8s
- 重编 meow_ui.a 并重链 meow_launcherd/ncm/files/hello/reader(picbrowser 无源码)
- fw_content.conf: 同步 launcherd/watchdog 新 md5;顺带修正 M35 遗留的 nyai 失效钉值

Host: test_wd_fw 66/66、test_overlay_gate 40/40、test_meow_time64/time/repaint PASS。
真机未验证(设备关机);未刷机、未写设备。
2026-11-10 22:51:00 +08:00

753 lines
40 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.
/* power_menu.c — MeowOS launcher 拆分模块(LAUNCHER_SPLIT_PLAN 拆分)。*/
/* 本文件由拆分工具从 meow_launcherd.c 按职责搬移;逻辑与原文 0-diff(仅 static 可见性按跨模块需求调整)。*/
#include "meow_launcher_internal.h"
/* M2.x 锁屏:电源菜单增「锁定」手动锁。网格 2 列×3 行:0=关机 1=重启 2=回HiBy 3=升级
* 4=锁定 5=取消;取消仍是安全默认项(PM_NUM-1),电源键确认/导航不会误触发关机。 */
static const int pm_cy[PM_NUM] = { 250, 250, 390, 390, 530, 530 }; /* 圆心 y(行距 140) */
static const char *pm_labels_cn[PM_NUM] = { "关机", "重启", "回HiBy", "升级", "锁定", "取消" };
static const char *pm_labels_en[PM_NUM] = { "POWER OFF", "RESTART", "HIBYOS", "UPGRADE", "LOCK", "CANCEL" };
/* 电源菜单圆钮图标:从 pm_icons.h 的 56×56 alpha 位图 blit 到 fb(以圆钮中心 cx,cy 为锚居中)。
* alpha 按 col(RGB565)映射:图标前景色随选中/未选中主题色变化(日夜间由 pm_theme_* 提供)。
* dst_px 为 fb16bpp 写入:新像素 = col 前景与背景(btn bg)按 alpha 混合。 */
static void blit_pm_icon(int idx, int cx, int cy, uint16_t col)
{
const unsigned char *src = pm_icons[idx].data;
int S = PM_ICON_SIZE;
int x0 = cx - S / 2, y0 = cy - S / 2;
for (int y = 0; y < S; y++) {
int yy = y0 + y;
if (yy < 0 || yy >= fb_yres) continue;
uint16_t *row = (uint16_t *)fb + yy * (fb_line / 2);
for (int x = 0; x < S; x++) {
int a = src[y * S + x];
if (a < 8) continue; /* 透明像素跳过 */
int xx = x0 + x;
if (xx < 0 || xx >= fb_xres) continue;
/* 前景 col 已含所需亮度;此处仅做软边 alpha 混合到背景,避免锯齿 */
uint16_t bg = row[xx];
if (a >= 240) { row[xx] = col; continue; }
int a4 = a >> 4; /* 0..15 */
int r = ((col >> 11) * a4 + ((bg >> 11) & 0x1F) * (16 - a4)) >> 4;
int g = (((col >> 5) & 0x3F) * a4 + ((bg >> 5) & 0x3F) * (16 - a4)) >> 4;
int b = ((col & 0x1F) * a4 + (bg & 0x1F) * (16 - a4)) >> 4;
row[xx] = (uint16_t)((r << 11) | (g << 5) | b);
}
}
}
#if 0 /* ================================================================
* M7.0b 退役:电源菜单圆钮改走 SDK 控件(meow_card/meow_iconbtn/自定义画布),
* 命中/选中/按压态全由 SDK 接管;本块保留作回退位(删除 #if 0 行恢复旧手绘)。
* ================================================================ */
/* 绘制电源菜单圆钮 i(MD3 圆形:外描边+内填充+圆内 Material 图标;选中高亮)。
* 圆内放 Material Symbols 位图,文字标签放圆下(Android 12 式:icon 上、label 下)。 */
static void draw_pm_button(int i, int selected)
{
int cx = pm_cx[i], cy = pm_cy[i];
/* 主题(日夜间由 pm_theme_* 决定;默认取 dark,后续接设置) */
const pm_theme_t *th = &pm_theme_dark;
/* 先铺一块比任何选中圈都大的卡片底色圆,盖掉上一态残留的选中描边(问题3:
* 焦点移走后 PM_R+2..PM_R+6 之间残留亮圈。以 0x1C2438 卡片色清底)。 */
fill_circle(cx, cy, PM_R + 10, 0x1C2438);
/* MD3 无边框高亮:选中=未选中底色稍亮填充(无描边圈),仅底色区分焦点;避免圆形描边残留。 */
if (selected)
fill_circle(cx, cy, PM_R, th->btn_sel_bg);
else
fill_circle(cx, cy, PM_R, th->btn_nor_bg);
/* 圆内 Material 图标(alpha 位图 + 主题前景色) */
blit_pm_icon(i, cx, cy, selected ? th->icon_sel : th->icon_nor);
uint32_t col = selected ? 0xFFFFFF : 0xC8D0E8;
/* 圆下文字标签(真字体/中文 或 点阵 ASCII 回退) */
int ly = cy + PM_R + 8;
if (mfont_ready) {
const char *lab = pm_labels_cn[i];
int lw = meow_font_width_text(lab, 18);
meow_font_drawtext((uint16_t *)fb, cx - lw / 2, ly, lab, rgb565(col), 18);
} else {
const char *lab = pm_labels_en[i];
int lw = (int)strlen(lab) * 5;
draw_text(cx - lw / 2, ly + 1, lab, 1, col);
}
}
#endif /* M7.0b 退役:电源菜单旧手绘圆钮 */
/* ==================================================================
* M7.0b 电源菜单控件化(launcher 无 meow_ui_run,手动驱动 SDK 控件):
* - 圆角卡 → meow_card(SDK 主题 surfaceContainerLow);
* - 六个圆钮 → meow_widget_custom_create(pm_icons 专有位图:关机/重启/回HiBy/升级/锁定/取消,
* 均 56×56 1:1 blit,无放大)。meow_iconbtn 分支保留(回HiBy/取消曾改走 sb_icons,因放大锯齿
* 定案回退 pm_icons,见 pm_sdk_icon[] 注释;后续 SDK 出更高清图标可再启用);
* - 命中/选中/按压态全走 SDK(meow_widget_hit/set_state/draw);
* - 主题 = 读 SDK meow_theme(单一权威),后台遮罩仍 dim_screen(会话骨架不动)。
* 控件 lazy 创建一次(owner=PM_SENTINEL_ACT 哨兵 activity),跨多次进入复用;
* show_power_menu() 进入时 push 哨兵、退出时 pop(owner 闸门使控件命中生效)。
* ================================================================== */
#define PM_SENTINEL_ACT 0x50 /* 电源菜单哨兵 activity(不与 launcher 其它页冲突) */
#define PM_BTN_SIZE (PM_R * 2) /* 圆钮 = 外接正方形边长(直径) */
/* 哪些 pm 图标走 SDK sb_icons 表(可用 meow_iconbtn 直接出图);-1=不在 → 自定义画布 blit pm_icons。
* M7.0b 曾把 2=回HiBy / 5=取消 走 meow_iconbtn(SB_ARROW_BACK / SB_CLOSE),但 meow_iconbtn 按
* 0.6*size 把 32×32 sb_icons 位图放大到 ~60px(>位图原生墨高),最近邻放大导致明显锯齿;且此尺寸
* 大于 M6.8 及更早电源菜单圆钮图标(pm_icons 56×56,1:1 无放大)。定案:这两钮也回退 pm_icons 专有
* 位图(PM_ICON_BACK/PM_ICON_CANCEL,同为 Material Symbols 56×56),统一其余四钮画法,恢复旧尺寸。
* pm_icons[] 全部专用 → meow_iconbtn 分支保留(后续 SDK 出更高清图标可再启用)。 */
static const int pm_sdk_icon[PM_NUM] = {
-1, /* 0 关机:pm_icons 专有(SDK 无 power 位图) */
-1, /* 1 重启:pm_icons 专有 */
-1, /* 2 回HiBy:pm_icons 专有(恢复 M6.8 56×56,避免 meow_iconbtn 放大锯齿) */
-1, /* 3 升级:pm_icons 专有 */
-1, /* 4 锁定:pm_icons 专有(SDK 无 lock 位图) */
-1, /* 5 取消:pm_icons 专有(同 2,恢复旧尺寸) */
};
/* 圆钮文案 i18n id(meow_str)。common.cancel 内嵌已有;power.* 需 TF 覆盖表/后续并入 SDK 内嵌。 */
static const char *pm_str_id[PM_NUM] = {
"power.off", "power.restart", "power.back_hiby",
"power.upgrade", "power.lock", "common.cancel",
};
static meow_widget *g_pm_card = NULL;
static meow_widget *g_pm_btns[PM_NUM] = {NULL};
static int g_pm_init = 0;
/* 按主题混合(pct 0..100:base 向 top 混 pct%,24-bit 每通道;近似 SDK state_tint/blend)。 */
static uint32_t pm_blend(uint32_t base, uint32_t top, int pct)
{
int br = (base >> 16) & 0xFF, bg = (base >> 8) & 0xFF, bb = base & 0xFF;
int tr = (top >> 16) & 0xFF, tg = (top >> 8) & 0xFF, tb = top & 0xFF;
int r = (br * (100 - pct) + tr * pct) / 100;
int g = (bg * (100 - pct) + tg * pct) / 100;
int b = (bb * (100 - pct) + tb * pct) / 100;
return ((uint32_t)r << 16) | ((uint32_t)g << 8) | (uint32_t)b;
}
/* 自定义画布:绘制 pm_icons 专有圆钮(图标不在 SDK sb_icons 表时用)。
* SDK 已把绘制 clip 设为控件矩形;圆形底色按 selected 取主题容器色,图标墨色随之;
* 禁用/按压态由本回调自叠(SDK 不替 W_CUSTOM 自动叠,meow_widget_draw_state_layer 铺方矩形,
* 圆形钮此处手工按圆域混合,更贴合)。 */
static void pm_render_cb(meow_widget *w, const meow_theme *th, void *ctx)
{
int i = (int)(intptr_t)ctx;
int rc[4]; meow_widget_rect(w, rc); /* 绝对坐标 x,y,w,h(launcher 侧 meow_widget 不透明) */
int cx = rc[0] + rc[2] / 2, cy = rc[1] + rc[3] / 2;
int r = (rc[2] < rc[3] ? rc[2] : rc[3]) / 2;
unsigned st = meow_widget_state(w);
int sel = (st & MEOW_WS_SELECTED) != 0;
int disabled = (st & MEOW_WS_ENABLED) == 0;
int pressed = (st & MEOW_WS_PRESSED) != 0;
uint32_t base = sel ? th->primaryContainer : th->surfaceContainer;
uint32_t fg = sel ? th->onPrimaryContainer : th->onSurface;
if (disabled) base = pm_blend(base, th->onSurface, 38); /* 禁用变暗(向灰) */
else if (pressed) base = pm_blend(base, th->onSurface, sel ? 24 : 12); /* 按压状态层 */
meow_gfx_fill_circle(cx, cy, r, base);
blit_pm_icon(i, cx, cy, rgb565(fg));
}
/* 自定义画布命中:圆形(圆心=控件中心,半径=size/2)。 */
static int pm_hit_cb(meow_widget *w, int x, int y, void *ctx)
{
(void)ctx;
int rc[4]; meow_widget_rect(w, rc);
int cx = rc[0] + rc[2] / 2, cy = rc[1] + rc[3] / 2;
int r = (rc[2] < rc[3] ? rc[2] : rc[3]) / 2;
int dx = x - cx, dy = y - cy;
return dx * dx + dy * dy <= r * r;
}
/* 创建卡片 + 六钮(owner 钉 = PM_SENTINEL_ACT,仅首进创建一次,复用)。 */
static void pm_widgets_create(void)
{
meow_ui_activity_push(PM_SENTINEL_ACT);
g_pm_card = meow_card_create(PM_CARD_X0, PM_CARD_Y0, PM_CARD_W, PM_CARD_H);
for (int i = 0; i < PM_NUM; i++) {
int sz = PM_BTN_SIZE;
int x = pm_cx[i] - sz / 2, y = pm_cy[i] - sz / 2;
if (pm_sdk_icon[i] >= 0)
g_pm_btns[i] = meow_iconbtn_create(x, y, sz, pm_sdk_icon[i]);
else
g_pm_btns[i] = meow_widget_custom_create(x, y, sz, sz,
pm_render_cb, pm_hit_cb,
(void *)(intptr_t)i);
}
meow_ui_activity_pop(); /* 回主 activity(控件 owner 已钉为哨兵) */
g_pm_init = 1;
}
/* 主题刷新:iconbtn 底色=surfaceContainer(与卡片 surfaceContainerLow 拉开对比)。
* custom 钮由 render_cb 读 th 自带;此处只补 iconbtn 的 opts.bg(跨开放切主题也跟手)。 */
static void pm_refresh_theme(void)
{
const meow_theme *th = meow_theme_get();
meow_widget_opts op; memset(&op, 0, sizeof op);
op.bg = th->surfaceContainer;
for (int i = 0; i < PM_NUM; i++)
if (g_pm_btns[i] && pm_sdk_icon[i] >= 0)
meow_widget_set_opts(g_pm_btns[i], &op);
}
/* i18n 文案(meow_str_zh:未知 id → 中文兜底)。TF 覆盖表/后续 SDK 内嵌并入后自动换语。 */
static const char *pm_label(int i)
{
return meow_str_zh(pm_str_id[i], pm_labels_cn[i]);
}
/* 绘制单个圆钮(SDK 控件)+ 圆下标签(主题色,选中亮/未选次色)。 */
static void pm_draw_button(int i, int selected)
{
meow_widget *w = g_pm_btns[i];
if (!w) return;
meow_widget_set_selected(w, selected);
meow_widget_draw(w);
const meow_ui_theme_t *th = s_ui_theme();
int cx = pm_cx[i], cy = pm_cy[i];
int ly = cy + PM_R + 8;
uint32_t col = selected ? th->on_surface : th->on_surface_variant;
if (mfont_ready) {
const char *lab = pm_label(i);
int lw = meow_font_width_text(lab, 18);
meow_font_drawtext((uint16_t *)fb, cx - lw / 2, ly, lab, rgb565(col), 18);
} else {
const char *lab = pm_labels_en[i];
draw_text(cx - (int)strlen(lab) * 5 / 2, ly + 1, lab, 1, col);
}
}
/* 标脏一个圆钮的整块区域(圆外接框 + 圆下标签),供局部 flush。 */
static void pm_button_dirty(int i)
{
int sz = PM_BTN_SIZE, cy = pm_cy[i];
meow_dirty_add(pm_cx[i] - sz / 2 - 4, cy - sz / 2 - 4, sz + 8, sz + PM_R + 44);
}
/* 命中检测:返回命中的钮下标(0..PM_NUM-1),否则 -1。 */
static int pm_hit_button(int x, int y)
{
for (int i = 0; i < PM_NUM; i++)
if (g_pm_btns[i] && meow_widget_hit(g_pm_btns[i], x, y)) return i;
return -1;
}
/* 绘制卡片 + 标题 + 底部提示 + 全部圆钮(进入/全刷时调用一次)。 */
static void pm_draw_card(void)
{
pm_refresh_theme();
if (g_pm_card) meow_widget_draw(g_pm_card);
const meow_ui_theme_t *th = s_ui_theme();
const char *title = "POWER MENU";
int title_y = PM_CARD_Y0 + 26;
if (mfont_ready)
meow_font_drawtext((uint16_t *)fb, (fb_xres - meow_font_width_text(title, 20)) / 2,
title_y, title, rgb565(th->on_surface), 20);
else
draw_text((fb_xres - (int)strlen(title) * 6) / 2, title_y, title, 2, th->on_surface);
const char *hint = meow_str_zh("power.hint_nav", "音量导航 电源确认");
int hint_y = PM_CARD_Y0 + PM_CARD_H + 10;
if (mfont_ready)
meow_font_drawtext((uint16_t *)fb, (fb_xres - meow_font_width_text(hint, 18)) / 2,
hint_y, hint, rgb565(th->on_surface_variant), 18);
else
draw_text((fb_xres - (int)strlen("VOLUME NAV POWER OK") * 5) / 2,
hint_y + 1, "VOLUME NAV POWER OK", 1, th->on_surface_variant);
}
/* ---- 电源管理服务 IPC 客户端(SYSTEM_FRAMEWORK_SERVICES §3.2:launcher 经服务触发电源动作)----
* launcher 不再直连 reboot(2)syscall/写 mtd5/写 boot 默认 —— 统一经 meow_svc_power 的
* `/usr/data/meow_power.sock`(`MEOW_IPC_POWER_SOCK` 可覆盖)发 MSG_POWER_ACTION。 */
#define PW_SOCK_DEFAULT "/usr/data/meow_power.sock"
static int pw_write_all(int fd, const uint8_t *b, size_t n)
{
size_t off = 0;
while (off < n) { ssize_t w = write(fd, b + off, n - off); if (w <= 0) return -1; off += (size_t)w; }
return 0;
}
static int pw_read_all(int fd, uint8_t *b, size_t n)
{
size_t off = 0;
while (off < n) { ssize_t r = read(fd, b + off, n - off); if (r <= 0) return -1; off += (size_t)r; }
return 0;
}
/* 发一帧并等应答(读超时 1.5s,服务不可达/无响应不至于卡死菜单)。 */
static int pw_xchg(int fd, uint16_t type, uint32_t req, const void *pl, uint32_t len,
uint8_t *rbuf, uint32_t rcap, uint16_t *rtype)
{
uint8_t hdr[MEOW_IPC_HDR_LEN];
uint32_t magic = MEOW_IPC_MAGIC, rlen = len;
uint16_t ver = MEOW_IPC_VERSION, t = type;
memcpy(hdr + 0, &magic, 4); memcpy(hdr + 4, &ver, 2); memcpy(hdr + 6, &t, 2);
memcpy(hdr + 8, &req, 4); memcpy(hdr + 12, &rlen, 4);
if (pw_write_all(fd, hdr, sizeof hdr) < 0) return -1;
if (len && pl && pw_write_all(fd, pl, len) < 0) return -1;
struct pollfd pf = { fd, POLLIN, 0 };
int pr = poll(&pf, 1, 1500);
if (pr <= 0) return -1;
uint8_t rhdr[MEOW_IPC_HDR_LEN];
if (pw_read_all(fd, rhdr, sizeof rhdr) < 0) return -1;
uint32_t rmagic, rlen2; uint16_t rver, rt;
memcpy(&rmagic, rhdr, 4); memcpy(&rver, rhdr + 4, 2); memcpy(&rt, rhdr + 6, 2);
memcpy(&rlen2, rhdr + 12, 4);
if (rmagic != MEOW_IPC_MAGIC || rver != MEOW_IPC_VERSION) return -1;
if (rlen2 > rcap) rlen2 = rcap;
if (rlen2 && pw_read_all(fd, rbuf, rlen2) < 0) return -1;
if (rtype) *rtype = rt;
return 0;
}
/* 经电源服务执行动作。返回 MEOW_OK 或错误码;res/reslen 可收状态文本。 */
int pm_power_ipc(uint32_t cmd, uint32_t flag, const char *arg, char *res, int reslen)
{
const char *sock = getenv("MEOW_IPC_POWER_SOCK");
if (!sock || !sock[0]) sock = PW_SOCK_DEFAULT;
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) return MEOW_ERR_IO;
struct sockaddr_un a;
memset(&a, 0, sizeof a);
a.sun_family = AF_UNIX;
strncpy(a.sun_path, sock, sizeof a.sun_path - 1);
if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { close(fd); return MEOW_ERR_IO; }
m_hello_t h;
memset(&h, 0, sizeof h);
snprintf(h.app_id, sizeof h.app_id, "%s", "os.meow.launcher");
h.version = 1;
uint8_t rbuf[256]; uint16_t rtype = 0;
if (pw_xchg(fd, MSG_HELLO, 0x100, &h, sizeof h, rbuf, sizeof rbuf, &rtype) < 0 || rtype != MSG_ACK) {
close(fd); return MEOW_ERR_IO;
}
m_ack_t ak;
memcpy(&ak, rbuf, sizeof ak);
if (ak.code != MEOW_OK) { close(fd); return (int)ak.code; }
m_power_action_t pa;
memset(&pa, 0, sizeof pa);
pa.cmd = cmd;
pa.flag = flag;
if (arg) snprintf(pa.arg, sizeof pa.arg, "%s", arg);
if (pw_xchg(fd, MSG_POWER_ACTION, 0x101, &pa, sizeof pa, rbuf, sizeof rbuf, &rtype) < 0 || rtype != MSG_POWER_STATUS) {
close(fd); return MEOW_ERR_IO;
}
m_power_status_t st;
memcpy(&st, rbuf, sizeof st);
close(fd);
if (res && reslen > 0) {
uint16_t ml = st.msg_len;
if (ml >= (uint16_t)reslen) ml = (uint16_t)(reslen - 1);
memcpy(res, st.msg, ml);
res[ml] = 0;
}
return (int)st.code;
}
/* launcher 侧统一电源动作入口:先转储未上卡通知(进程本地,见 §3.2 定案),再经服务 IPC 执行。
* 返回 MEOW_OK=已受理(服务将执行,通常随即 reboot/poweroff);非 OK=服务不可达/拒绝,调用方应
* 稳妥处理(如菜单按「取消」返回,绝不误报动作成功导致 launcher 退出卡死)。 */
int meow_power_action(uint32_t cmd)
{
meow_notif_before_poweroff(); /* 幂等安全(未 init 直接返回) */
int rc = pm_power_ipc(cmd, 0, NULL, NULL, 0);
if (rc != MEOW_OK)
logmsg("meow_power_action cmd=%u FAILED rc=%d (power service unavailable?)", cmd, rc);
return rc;
}
/* M40 F2/P2:关闭菜单时恢复进入前的背景帧并 present。
* dim_screen() 就地压暗 back_buf;若不恢复,app_launcher 关闭会话时的 pm_capture_app_frame()
* 会把「压暗 App 帧 + 菜单卡」当成 App 帧拍回并上屏 → 菜单关闭后屏幕仍是菜单画面,
* 用户看到「再次单击没反应」(P2 主因)。所有退出路径(取消/锁定/关机/强关)统一恢复。 */
static void pm_restore_bg(unsigned char *snap, size_t len)
{
if (!snap || !len || !back_buf_ok || !fb) return;
memcpy(fb, snap, len);
meow_dirty_full();
meow_ui_flush();
}
/* 电源菜单(M2.6):2×2 网格,dirty 绘制消频闪;输入修复:
* - 触摸按下捕获坐标、抬起命中按钮即确认(同 run_menu MT 防抖);
* - 音量上/下导航;播放=确认;电源新按(松开长按后)=确认当前钮(非取消);
* - 取消=选「取消」钮;按住 power.forceoff_ms(缺省 8s,从按下起算)=强制关机(绕过)。
* 返回 1=采取动作,0=取消。 */
int show_power_menu(void)
{
int fbfd = open(FB_DEV, O_RDWR);
if (fbfd >= 0) { ioctl(fbfd, FBIOBLANK, 0); close(fbfd); } /* 亮起面板 */
meow_screen_on = 1; /* 菜单打开即视为亮屏 */
/* M40 F2:先开输入设备并采样第一轮基线。旧顺序把这一步放在 pm_capture/dim/建卡之后
* (真机实测菜单请求→keypoll ready 相隔 75ms),一次完整的按下+抬起若落在这段阻塞绘制里
* 就永远看不到边沿(EVIOCGKEY 是电平采样,不回溯事件队列)。重活挪到采样之后。 */
meow_touch mt;
mt_reset(&mt);
int tfd = open(DEV_TOUCH, O_RDONLY | O_NONBLOCK);
struct keypoll kp_power, kp_keys;
kp_open(&kp_power, DEV_POWER);
kp_open(&kp_keys, DEV_KEYS);
int prev_vold = 0, prev_volu = 0, prev_pwr = 0;
kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL);
prev_pwr = kp_is_down(&kp_power, KEY_POWER) ? 1 : 0;
prev_vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN) ? 1 : 0;
prev_volu = kp_is_down(&kp_keys, KEY_VOLUMEUP) ? 1 : 0;
/* 背景帧:先把 app 当前帧读回 back_buf,快照后再压暗绘制 */
pm_capture_app_frame(); /* A-0:菜单背景 = app 当前帧(fb0 buffer0) */
unsigned char *pm_bg_snap = NULL;
size_t pm_bg_len = 0;
if (back_buf_ok && fb && fb_line > 0 && fb_yres > 0) {
pm_bg_len = (size_t)fb_line * (size_t)fb_yres;
pm_bg_snap = malloc(pm_bg_len);
if (pm_bg_snap) memcpy(pm_bg_snap, fb, pm_bg_len);
else { pm_bg_len = 0; logmsg("pm: bg snapshot malloc fail (len=%zu)", pm_bg_len); }
}
dim_screen();
/* M2.8 Bug 3-residual:若电源菜单是在「息屏」态打开(按住 3s),此处 FBIOBLANK(0)
* 刚上电,LCDC 扫描地址可能非 buffer0;强制 re-pan 让压暗的应用列表 + 菜单落在被显示的
* 缓冲,否则菜单画在非显示缓冲上不可见。 */
fb_pan_buf0_force();
/* M2.11 B:默认光标 = 「取消」(安全项,index 4)。用户打开菜单后未导航就按电源/播放,
* 只会取消关菜单,绝不因「按电源键」本身关机/重启。要真关机必须先导航到「关机」再确认。 */
int sel = PM_NUM - 1; /* 默认取消(安全) */
/* M7.0b 控件化:进入 push 哨兵(控件 owner 闸门使 meow_widget_hit 生效)+ lazy 建卡/钮。
* 卡片=meow_card(SDK 主题 surfaceContainerLow),圆钮=meow_iconbtn/自定义画布,命中/选中/
* 按压态全走 SDK;背景遮罩仍 dim_screen(会话骨架不动)。 */
meow_ui_activity_push(PM_SENTINEL_ACT);
if (!g_pm_init) pm_widgets_create();
pm_draw_card(); /* 卡片 + 标题 POWER MENU + 底部提示(SDK 主题色,i18n 文案) */
meow_dirty_full(); /* M2.16:把压暗列表+卡片+标题+(下方圆钮)作为一整帧呈现 */
int act = -1;
int pm_rc = 0;
unsigned long long pm_t0 = now_ms();
/* 强关阈值:power.forceoff_ms 是**从按下起算**的总时长;菜单在 longpress 阈值处打开,
* 因此菜单内剩余等待 = forceoff - longpress(缺省 8000-3000=5000ms,与原硬编码值一致,
* 但不再忽略 settings)。配置异常(剩余<1s)时退回完整 forceoff。 */
unsigned long long pm_forceoff_after_ms = 5000;
if (power_forceoff_ms > power_longpress_menu_ms &&
power_forceoff_ms - power_longpress_menu_ms >= 1000)
pm_forceoff_after_ms = (unsigned long long)(power_forceoff_ms - power_longpress_menu_ms);
else if (power_forceoff_ms > 0)
pm_forceoff_after_ms = (unsigned long long)power_forceoff_ms;
unsigned long long pm_last_interact = pm_t0; /* M2.33:菜单空闲自动关闭基准(任何互动刷新) */
int pm_init = 0, last_sel = -1;
/* M2.10 Bug:电源菜单强关(按住电源到 forceoff)会覆盖用户的菜单操作 —— 用户按住
* 电源打开菜单后(尤其仍按着电源)点选「回到HibyOS/重启」等按钮,强关一到先触发 → 关机
* 而非执行所选动作。修法:一旦用户与菜单互动(音量导航/触摸/播放/电源确认),置 pm_interacted=1
* 取消强关,让所选动作(如「回到HibyOS」→ reboot RESTART)生效。仅当全程未互动才保留「按住
* 到 forceoff=强关」。*/
int pm_interacted = 0;
int pm_press_idx = -1; /* M7.0b:当前按下钮(按压态走 SDK 状态机位) */
for (;;) {
/* dirty 绘制:首次全画;此后仅选中变化才重画受影响两钮(消频闪)→ 标脏 + present。 */
if (!pm_init) {
for (int i = 0; i < PM_NUM; i++) pm_draw_button(i, i == sel);
pm_init = 1; last_sel = sel;
meow_ui_flush(); /* A-1/A-2:SDK present(vsync+帧率门控+blit 脏=全帧,含压暗列表+标题+按钮) */
} else if (sel != last_sel) {
pm_draw_button(last_sel, 0);
pm_draw_button(sel, 1);
pm_button_dirty(last_sel);
pm_button_dirty(sel);
last_sel = sel;
meow_ui_flush(); /* A-1/A-2:SDK present,blit 两钮区 */
}
if (sim_power_pick >= 0 && now_ms() - pm_t0 >= 2000) act = sim_power_pick;
int vold = 0, volu = 0, play = 0, pwr = 0;
if (kp_keys.ok) { kp_poll(&kp_keys, NULL, NULL);
vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN);
volu = kp_is_down(&kp_keys, KEY_VOLUMEUP);
play = kp_is_down(&kp_keys, KEY_PLAYPAUSE); }
if (kp_power.ok) { kp_poll(&kp_power, NULL, NULL); pwr = kp_is_down(&kp_power, KEY_POWER); }
if (vold && !prev_vold) { sel = (sel + 1) % PM_NUM; pm_interacted = 1; pm_last_interact = now_ms(); }
if (volu && !prev_volu) { sel = (sel - 1 + PM_NUM) % PM_NUM; pm_interacted = 1; pm_last_interact = now_ms(); }
/* M2.19:播放键不再作确认(让位给全局媒体控制)。电源菜单的物理确认
* 已由电源键负责(下行),播放键删除,重复确认移除。 */
(void)play;
if (pwr && !prev_pwr) { act = sel; pm_interacted = 1; pm_last_interact = now_ms(); } /* 电源新按=确认(非取消) */
prev_vold = vold; prev_volu = volu; prev_pwr = pwr;
/* 触摸:按下(meow_touch 原点锁定,goodix 时序免疫),抬起按按钮命中确认 */
if (tfd >= 0) {
struct pollfd pfd = { tfd, POLLIN, 0 };
if (poll(&pfd, 1, 0) > 0) {
unsigned char ev[16];
while (read(tfd, ev, sizeof ev) == (ssize_t)sizeof ev) {
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) mt_abs(&mt, val, mt.last_y);
else if (code == ABS_Y || code == ABS_MT_POSITION_Y) mt_abs(&mt, mt.last_x, val);
} else if (type == EV_KEY && code == BTN_TOUCH) {
if (val && !mt.down) {
mt_down(&mt);
pm_interacted = 1;
pm_last_interact = now_ms();
/* M7.0b 按压态:按下命中钮 → 置 PRESSED 并重绘(SDK 状态层,实时) */
pm_press_idx = pm_hit_button(mt.down_x, mt.down_y);
if (pm_press_idx >= 0) {
meow_widget_set_state(g_pm_btns[pm_press_idx], MEOW_WS_PRESSED, 1);
meow_widget_draw(g_pm_btns[pm_press_idx]);
pm_button_dirty(pm_press_idx);
meow_ui_flush();
}
} else if (!val && mt.down) {
pm_interacted = 1;
pm_last_interact = now_ms();
if (pm_press_idx >= 0) { /* 清按压态(SDK 状态层随 PRESSED 位清除) */
meow_widget_set_state(g_pm_btns[pm_press_idx], MEOW_WS_PRESSED, 0);
meow_widget_draw(g_pm_btns[pm_press_idx]);
pm_button_dirty(pm_press_idx);
pm_press_idx = -1;
meow_ui_flush();
}
int dx, dy;
/* M2.25:抬手位移 >= MEOW_TOUCH_SWIPE_THRESH 判滑动(返回0),
* 不做确认;否则有效 tap,按下原点命中的按钮即确认。 */
if (mt_up(&mt, &dx, &dy)) {
/* M7.0b 命中走 SDK(owner==哨兵 + enabled + 圆形命中) */
int hit = pm_hit_button(mt.down_x, mt.down_y);
if (hit >= 0) { sel = hit; act = hit; }
}
}
}
}
}
}
/* 强关(绕过):仅当电源持续按住、且用户全程未与菜单互动才触发 —— 放在触摸/按键处理
* 之后,避免「用户已点选按钮」的同一轮被强关抢先(M2.10 Bug)。
* M40:阈值接 power.forceoff_ms(从按下起算的剩余时长),不再硬编码 5000/日志谎称 8s。 */
if (pwr && !pm_interacted && now_ms() - pm_t0 >= pm_forceoff_after_ms) {
if (sim_no_exec) {
logmsg("pm: [sim-no-exec] POWER HOLD %dms (menu-open + %llums) -> would force poweroff",
power_forceoff_ms, pm_forceoff_after_ms);
pm_rc = 1;
goto out;
}
logmsg("pm: POWER HOLD %dms (menu-open + %llums) -> force poweroff",
power_forceoff_ms, pm_forceoff_after_ms);
if (meow_power_action(POWER_SHUTDOWN) != MEOW_OK) {
logmsg("pm: power service unavailable, abort force-off (menu closed, no action)");
pm_rc = 0;
goto out;
}
pm_rc = 1;
goto out;
}
/* M2.33 菜单空闲超时自动关闭:菜单受 MeowOS screen_off_timeout 管理(系统界面不豁免)。
* 超过超时无任何互动 → 关闭菜单(视为取消)并自动息屏。目的是:息屏后单击电源亮屏时,
* 电源按下信号不会命中菜单按钮(菜单已关),杜绝误触关机/执行菜单动作。 */
if (screen_off_timeout > 0 &&
(unsigned long long)(now_ms() - pm_last_interact) >= (unsigned long long)screen_off_timeout * 1000) {
logmsg("pm: idle %ds -> close menu + auto screen-off (act=cancel)", screen_off_timeout);
meow_panel_off();
meow_auto_slept = 1;
act = PM_NUM - 1; /* 取消(关菜单) */
break;
}
if (act >= 0) break;
usleep(POLL_MS * 1000);
}
/* M2.11 B:电源键在菜单 = 按当前项确认。act 与电源动作逐项核对:
* act==PM_NUM-1(取消) -> 只关菜单,绝不关机(rc=0,永不到电源动作);
* act==4(锁定) -> 手动锁(meow_lock_screen_engage,未配 PIN 则 no-op),关菜单;
* act==0(关机)/act==1(重启)/act==2(回HibyOS)/act==3(升级模式) -> 才经服务 IPC 执行电源动作。
* 只有 act 为 0/1/2/3 才可能发电源动作;power 键本身绝不触发关机。 */
logmsg("pm: act=%d -> %s", act,
act == 0 ? "关机" : act == 1 ? "重启" : act == 2 ? "回HibyOS" : act == 3 ? "升级模式"
: act == 4 ? "锁定" : "取消");
if (act == PM_NUM - 1) { logmsg("pm: cancel (close menu only)"); pm_rc = 0; goto out; }
if (act == 4) {
logmsg("pm: LOCK SCREEN");
meow_lock_screen_engage();
pm_rc = 0; /* 关菜单;run_menu 继续(锁屏页已阻塞到解锁或电源息屏) */
goto out;
}
/* 动作统一经电源管理服务 IPC(meow_svc_power);launcher 不再直连 poweroff/reboot(2)/写 mtd5/
* 写 boot 默认——关机/重启/升级/回HiBy 的动作原语都在服务内实现(§3.2 单一动作链路)。 */
/* 公共服务:执行动作;服务不可达 → 按「取消」返回(launcher 存活,绝不误报已动作卡死)。
* meow_power_action 内部已先 meow_notif_before_poweroff()(转储未上卡通知)。 */
pm_rc = 1;
if (act == 0) {
logmsg("pm: POWER OFF");
if (sim_no_exec) { logmsg("pm: [sim-no-exec] would poweroff"); goto out; }
if (meow_power_action(POWER_SHUTDOWN) != MEOW_OK) {
logmsg("pm: power service unavailable, abort (menu closed, no action)");
pm_rc = 0;
}
} else if (act == 1) {
logmsg("pm: REBOOT");
if (sim_no_exec) { logmsg("pm: [sim-no-exec] would reboot"); goto out; }
if (meow_power_action(POWER_REBOOT) != MEOW_OK) {
logmsg("pm: power service unavailable, abort (menu closed, no action)");
pm_rc = 0;
}
} else if (act == 2) {
logmsg("pm: RETURN TO HIBYOS");
if (sim_no_exec) { logmsg("pm: [sim-no-exec] would reboot to hiby"); goto out; }
if (meow_power_action(POWER_BACK_HIBY) != MEOW_OK) {
logmsg("pm: power service unavailable, abort (menu closed, no action)");
pm_rc = 0;
}
} else if (act == 3) {
logmsg("pm: ENTER UPGRADE");
if (sim_no_exec) { logmsg("pm: [sim-no-exec] would enter upgrade"); goto out; }
if (meow_power_action(POWER_UPGRADE) != MEOW_OK) {
logmsg("pm: power service unavailable, abort (menu closed, no action)");
pm_rc = 0;
}
}
out:
if (tfd >= 0) close(tfd);
meow_ui_activity_pop(); /* M7.0b:退出电源菜单,回主 activity(控件 owner 仍钉哨兵,复用) */
/* M40 F2:恢复进入前的背景帧(否则 app_launcher 会把菜单帧当 App 帧上屏 = P2)。
* 放在动作执行之后:锁屏动作已 meow_panel_off(),此时恢复只写 fb0(面板黑),
* 下次唤醒 / App 整帧重画时得到的是正确底层帧而不是 PIN/菜单帧。 */
pm_restore_bg(pm_bg_snap, pm_bg_len);
if (pm_bg_snap) free(pm_bg_snap);
return pm_rc;
}
/* M2.6 G「Reboot to player」:MeowOS 常驻模式点「返回播放器」时,播放器已死,弹确认框
* 「返回播放器需重启,现在重启?[是/否]」。是=写选单默认 HibyOS + reboot;否=留在 MeowOS。
* VOL=是/否 切换;PLAY=确认;POWER=取消;触摸点 是/否 确认。风格同电源菜单。
* 返回 1=是(已 reboot,通常不回),0=否/取消(调用方重绘应用列表)。 */
/* 「返回播放器」确认框「是/否」按钮(左右),选中高亮。
* M2.9 Bug 4:改为 dirty 绘制 —— 仅 sel 变化时重画两钮(旧钮取消高亮 + 新钮高亮),
* 不随主循环每 10ms 全刷(原实现每帧重画同一内容,与 LCD 刷新竞争产生频闪)。 */
static void draw_cr_button(int sel, int i)
{
const meow_theme *mt = meow_theme_get(); /* M7.5:主题语义色(深/浅×MD3/MIUIx 单一权威) */
int x0 = (i == 0) ? 80 : 280, y0 = 420, w = 120, h = 60;
uint32_t bg = (i == sel) ? mt->primary : mt->surfaceContainer;
fill_round_rect(x0, y0, w, h, 12, bg);
/* M7.0b i18n:是/否 经 meow_str 抽取(未知 id 回退中文) */
const char *lbl = (i == 0) ? meow_str_zh("power.confirm_yes", "是")
: meow_str_zh("power.confirm_no", "否");
uint32_t fg = (i == sel) ? mt->onPrimary : mt->onSurface;
if (mfont_ready) {
int lw = meow_font_width_text(lbl, 20);
meow_font_drawtext((uint16_t *)fb, x0 + w / 2 - lw / 2, y0 + 30 - meow_font_line_height(20) / 2,
lbl, rgb565(fg), 20);
} else {
draw_text(x0 + w / 2 - 6, y0 + 24, (i == 0) ? "YES" : "NO", 2, fg);
}
}
int confirm_reboot_player(void)
{
dim_screen();
/* M2.8 Bug 3-residual:确认框同电源菜单 —— 若打开前刚经历息屏/blank,LCDC 扫描地址可能
* 非 buffer0;强制 re-pan 让「压暗列表 + 是/否」落在被显示的缓冲。 */
fb_pan_buf0_force();
const meow_theme *mt0 = meow_theme_get(); /* M7.5:标题颜色走主题语义色 */
if (mfont_ready) meow_font_drawtext((uint16_t *)fb, 20, 320,
meow_str_zh("power.reboot_confirm", "返回播放器需重启,现在重启?"), rgb565(mt0->onSurface), 20);
else draw_text(20, 322, "REBOOT TO RETURN TO PLAYER?", 2, mt0->onSurface);
meow_touch mt;
mt_reset(&mt);
int tfd = open(DEV_TOUCH, O_RDONLY | O_NONBLOCK);
struct keypoll kp_power, kp_keys;
kp_open(&kp_power, DEV_POWER);
kp_open(&kp_keys, DEV_KEYS);
int prev_vold = 0, prev_volu = 0, prev_pwr = 0;
kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL);
prev_pwr = kp_is_down(&kp_power, KEY_POWER) ? 1 : 0;
prev_vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN) ? 1 : 0;
prev_volu = kp_is_down(&kp_keys, KEY_VOLUMEUP) ? 1 : 0;
int sel = 1; /* 默认「否」:不误重启 */
int act = -1;
int cr_init = 0, cr_last_sel = -1; /* M2.9 Bug 4:dirty——仅 sel 变化才重画两钮 */
/* 标题/按键提示:打开时画一次(原实现把它们放主循环每 10ms 全刷,与 LCD 刷新竞争 →
* 「是/否」钮频闪;改为只在打开/状态变化时绘制)。标题在前面已画一次,这里画提示行。 */
if (mfont_ready) meow_font_drawtext((uint16_t *)fb, 20, 560,
meow_str_zh("power.confirm_hint", "音量切换 电源取消"), rgb565(mt0->onSurfaceVariant), 20);
else draw_text(20, 562, "VOL NAV POWER EXIT", 1, mt0->onSurfaceVariant);
meow_dirty_full(); /* M2.16:压暗列表+标题+提示作为一整帧呈现 */
for (;;) {
/* dirty 绘制:首次全画两钮;此后仅 sel 变化才重画(消频闪)→ 标脏 + present。 */
if (!cr_init) {
for (int i = 0; i < 2; i++) draw_cr_button(sel, i);
cr_init = 1; cr_last_sel = sel;
meow_ui_flush();
} else if (sel != cr_last_sel) {
draw_cr_button(sel, cr_last_sel); /* 旧选中:取消高亮 */
draw_cr_button(sel, sel); /* 新选中:高亮 */
meow_dirty_add((cr_last_sel == 0 ? 80 : 280) - 10, 412, 140, 76);
meow_dirty_add((sel == 0 ? 80 : 280) - 10, 412, 140, 76);
cr_last_sel = sel;
meow_ui_flush();
}
int vold = 0, volu = 0, play = 0, pwr = 0;
if (kp_keys.ok) { kp_poll(&kp_keys, NULL, NULL);
vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN); volu = kp_is_down(&kp_keys, KEY_VOLUMEUP);
play = kp_is_down(&kp_keys, KEY_PLAYPAUSE); }
if (kp_power.ok) { kp_poll(&kp_power, NULL, NULL); pwr = kp_is_down(&kp_power, KEY_POWER); }
if (vold && !prev_vold) sel = (sel + 1) % 2;
if (volu && !prev_volu) sel = (sel - 1 + 2) % 2;
/* M2.19:播放键不再作「是/确认」(让位给全局媒体控制)。确认框的确认
* 走触摸点选(下方 tfd 命中),电源键=取消(下行);播放键删除。 */
(void)play;
if (pwr && !prev_pwr) act = 3; /* 电源=取消 */
prev_vold = vold; prev_volu = volu; prev_pwr = pwr;
if (tfd >= 0) {
struct pollfd pfd = { tfd, POLLIN, 0 };
if (poll(&pfd, 1, 0) > 0) {
unsigned char ev[16];
while (read(tfd, ev, sizeof ev) == (ssize_t)sizeof ev) {
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) mt_abs(&mt, val, mt.last_y);
else if (code == ABS_Y || code == ABS_MT_POSITION_Y) mt_abs(&mt, mt.last_x, val);
} else if (type == EV_KEY && code == BTN_TOUCH) {
if (val && !mt.down) {
mt_down(&mt);
} else if (!val && mt.down) {
int dx, dy;
if (mt_up(&mt, &dx, &dy)) {
for (int i = 0; i < 2; i++) {
int x0 = (i == 0) ? 80 : 280;
/* 命中「是/否」钮:x∈[x0-8, x0+128], y∈[412, 488](含端点) */
if (mt_in(&mt, x0 - 8, 412, 137, 77))
{ sel = i; act = i; break; }
}
}
}
}
}
}
}
if (act >= 0) break;
usleep(POLL_MS * 1000);
}
if (tfd >= 0) close(tfd);
if (act == 0) {
logmsg("reboot-to-player: YES -> default HibyOS + reboot");
if (sim_no_exec) { logmsg("reboot-to-player: [sim-no-exec] would reboot to hiby"); return 1; }
if (meow_power_action(POWER_BACK_HIBY) != MEOW_OK) {
logmsg("reboot-to-player: power service unavailable, abort (stay in MeowOS)");
return 0;
}
return 1;
}
logmsg("reboot-to-player: cancel");
return 0;
}