215: meow_font.c 流式分块解码(gbk峰值40→23.5MB, 逐字节校验一致) + 多字体槽5→4+16MB驻留预算; 量化: gbk稳态驻留17.3MB vs common 4.2MB(OOM主因); meow_font.a→67113c03 meow_ui.a→223d5401 216: bootmenu让位(sysui在则start launcher让位, 不在则exec兜底) + S70预置launcher.conf enabled=1(+旧设备升级迁移); launcher崩溃可自动拉起; bootmenu 4441822d / S70 e435affc / S90 e0ace111 216注: S70早启动与选单共存为真机重点观察项
1714 lines
79 KiB
C
1714 lines
79 KiB
C
/* meow_bootmenu.c — R3 Pro II MeowOS/HibyOS 开机引导选单(阶段0 · M0 二进制自检版)
|
||
*
|
||
* M2.14 热键一次触发修复(2026-08-29,MEOW_BOOTMENU_VER 0.1.0-m2.14):
|
||
* 开机武装延迟(ARM_DELAY_MS) + 播放器忙判门(proc_is_busy) 不再「静默丢弃」热键——
|
||
* 命中即置 hotkey_pending,待条件满足(延迟结束/播放器不忙)后自动弹确认框
|
||
* (用户只按一次,不需第二下)。新增 --sim-hotkey-strict 让 sim 走该门(测 pending)。
|
||
*
|
||
* M2.26 触摸确认迁移(2026-08-29,MEOW_BOOTMENU_VER 0.1.0-m2.26):选单触摸 + 热键
|
||
* 确认框触摸从各自 t_last_x/y + t_down_x/y 解析迁到 meow_touch 统一接口(mt_reset/
|
||
* mt_abs/mt_down/mt_up;goodix 时序免疫:BTN_TOUCH=1 不锁原点,等按下后首个有效 ABS
|
||
* 才锁,位移>=65 判滑动不命中)——修「触摸确认要点两下」(电源键一下即可确认)。
|
||
* 绘制/选单逻辑/超时倒计时/fork 播放器/supervisor/热键触发核心路径不动。
|
||
*
|
||
* M2 诊断 · 信号记录 hook(仅记录,不改行为;排查「HibyOS 播放器 UI 触发关机/升级 →
|
||
* 卡最后一帧但系统没真关机/SSH 能连」第一步):supervisor 加 bm_log() 写 TF 卡
|
||
* MeowOS/logs/meow_boot_<bootts>.log(卡不可用降级 stderr),记录 supervisor
|
||
* **收到**的信号(SIGTERM/SIGINT/SIGQUIT/SIGHUP——handler 只置标志,主循环 log 后
|
||
* signal(s,SIG_DFL)+raise(s) 恢复默认重抛,与未加 hook 一致终止)、**发出**给播放器的
|
||
* 信号(SIGSTOP/SIGCONT/SIGTERM/SIGKILL)、以及**播放器退出原因**(WTERMSIG/WEXITSTATUS)。
|
||
* 不转发信号、不善后、不 SHOW 关机画面、不截断。
|
||
*
|
||
* M2.29 supervisor 接管关机/升级(2026-08-29,MEOW_BOOTMENU_VER 0.1.0-m2.29-takeover):
|
||
* 基于 M2 诊断 hook 抓到的两条真机信号,让 supervisor 把「播放器 UI 触发关机/升级 →
|
||
* 卡最后一帧但系统没真关机(SSH 能连)」收束掉:
|
||
* - 播放器**自然退出态 0**(UI 关机,非热键 SIGTERM 杀):waitpid 识别 WIFEXITED&&0
|
||
* → supervisor 执行真关机(sync + reboot(2) POWER_OFF),不卡最后一帧。
|
||
* 区分:热键切 MeowOS 的 SIGTERM 杀仍在 confirm 分支内 waitpid,不进自然退出检测。
|
||
* - mtd5(ota 标记分区) 变 ota:kernel2(播放器 UI 升级 → nandwrite 写 mtd5):supervisor
|
||
* 主循环每 500ms 读一次 /dev/mtd5 头字节(nandwrite 在 offset 0 写 256B 定长标记,
|
||
* 只读 ~15B,IO 极低),命中即判升级 → reboot(2) RESTART → uboot 读 mtd5=ota:kernel2
|
||
* 引导 kernel2(升级模式,自动刷 TF 卡 r3proii.upt)。不卡最后一帧。
|
||
* 说明:/proc/mtd 只反映分区表不变读,无法观察内容变化,故用轻量读 /dev/mtd5 轮询。
|
||
* 两根都写 bm_log(supervisor 接管 shutdown/upgrade)。
|
||
*
|
||
* ============================================================================
|
||
* WHY (设计见 BOOT_SELECTOR_DESIGN.md §3)
|
||
* ============================================================================
|
||
* - 开机最先显示选单(HibyOS / MeowOS),选择后 fork 播放器(HibyOS)或进入
|
||
* MeowOS 主屏;HibyOS 模式热键(电源+音量减)杀播放器接管。
|
||
* - M2.83 launcher 监管迁移:MeowOS 主屏(meow_launcherd)生命周期已交 meow_sysui
|
||
* (S70 起)。故选 MeowOS 时——sysui 在 → 让 sysui 拉起/监管 launcher,本进程让位退出
|
||
* (rcS 推进,S92 见 mode=meow 跳过播放器);sysui 不在 → 本进程照旧 exec launcher(兜底,
|
||
* 防开不了机)。选 HibyOS 时 sysui 在 → 让 sysui 停 launcher(不与播放器抢 fb0)。
|
||
* - 本二进制 = 选单本体(M0)。M0 只做二进制自检:SSH 推 /data 独立跑,
|
||
* **不进启动链**(不碰 S90/S92/播放器/SSH 顺序)。
|
||
*
|
||
* M0 自检目标(BOOT_SELECTOR_DESIGN.md §9 M0):
|
||
* --dumpkeys N / --sim-select H|M / --boot-only / --version 验证
|
||
* fb0 初始化、绘制、选择/超时决策、CLI 钩子。**默认不 fork 播放器**——
|
||
* M0 严禁触碰到正在运行的 hiby_player(设备上它正由 init 拉起)。
|
||
*
|
||
* ⚠️ M0 记号(BOOT_SELECTOR_DESIGN.md §2.3 播放器 fork 契约已按 §3.6 写好,
|
||
* 但本阶段默认关闭):fork_hiby_player() 实现了「env -i 复刻 11 变量 +
|
||
* chdir("/") + stdio 重定向 + execve /usr/bin/hiby_player」的完整契约,
|
||
* 仅在传 --fork 时才会真正 exec。M2 接管 HibyOS 时由 S90 传 --fork 打开。
|
||
*
|
||
* ⚠️ 字体限制:本设备的 5x7 点阵字体(与 meow_launcherd/hello 相同)只含
|
||
* ASCII(0-9 A-Z a-z 空格 . ! - _),**无 CJK 字形**。设计稿中的中文标签
|
||
* (如「[HIBYOS 官方播放器]」)无法用该字体渲染,故本版用 ASCII 标签
|
||
* ([HIBYOS PLAYER] / [MEOWOS HOME]),CJK 渲染留待后续引入 TTF 渲染器。
|
||
*
|
||
* fb0 要点(ANALYSIS.md §18.18,勿写反):
|
||
* FBIOGET_VSCREENINFO=0x4600 / FBIOGET_FSCREENINFO=0x4602 / FBIOPAN_DISPLAY=0x4606
|
||
* 本机 xres=480 yres=720 bpp=16 line=960 yres_virtual=1440(双缓冲)ypanstep=1
|
||
* mmap 整个虚拟帧缓冲(含两帧);画前 FBIOPAN_DISPLAY pan 到 yoffset=0(buffer0)。
|
||
* 显示哪个缓冲由 fb_var.yoffset 决定——播放器电源/息屏重绘可能把 yoffset 留在
|
||
* buffer1,选单画在 buffer0 而面板显示 buffer1 会「选单不可见/屏幕冻结」(v6.5 bug)。
|
||
*
|
||
* 静态 MIPS32r2 musl(soft-float)编译:
|
||
* ~/Project/R3ProII/.tools/zig-mipsel-cc -Os -static meow_bootmenu.c -o meow_bootmenu
|
||
*/
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <stdarg.h>
|
||
#include <unistd.h>
|
||
#include <fcntl.h>
|
||
#include <errno.h>
|
||
#include <signal.h>
|
||
#include <stdint.h>
|
||
#include <time.h>
|
||
#include <sys/ioctl.h>
|
||
#include <sys/mman.h>
|
||
#include <sys/poll.h>
|
||
#include <sys/wait.h>
|
||
#include <sys/stat.h>
|
||
#include <sys/syscall.h> /* M2.29:supervisor 接管关机/升级,直连 reboot(2) syscall */
|
||
#include <linux/input.h>
|
||
#include "meow_touch.h"
|
||
|
||
/* ---- 本设备 fb ioctl 号(实测,勿写反)---- */
|
||
#define FBIOGET_VSCREENINFO 0x4600
|
||
#define FBIOGET_FSCREENINFO 0x4602
|
||
#define FBIOPAN_DISPLAY 0x4606
|
||
|
||
/* ---- 路径 ---- */
|
||
#define DEV_POWER "/dev/input/event0" /* md-gpio-keys: KEY_POWER(116) */
|
||
#define DEV_KEYS "/dev/input/event2" /* jz adc keyboard: VOL(114/115) PLAY(164) */
|
||
#define DEV_TOUCH "/dev/input/event1" /* goodix-ts(未 grab,可共享读) */
|
||
#define FB_DEV "/dev/fb0"
|
||
#define MEOWAPPS "/usr/data/mnt/sd_0/MeowAPPs"
|
||
#define CONF_FILE MEOWAPPS "/meow.conf" /* TF 卡配置:boot_timeout_ms/boot_menu/bg_color;boot_default 权威在板载 /data/meow_boot_default */
|
||
#define MODE_FILE "/tmp/meow_boot_mode" /* 开机模式标记(tmpfs,开机清空) */
|
||
#define PLAYER_BIN "/usr/bin/hiby_player"
|
||
#define PLAYER_LOG "/usr/data/meow_player.log"
|
||
#define LAUNCHER_BIN "/usr/bin/meow_launcherd" /* MeowOS 基础主屏(M2 接管) */
|
||
|
||
/* ---- 面板/背光(与 meow_launcherd 相同路径,§18.21/§红线)---- */
|
||
#ifndef FBIOBLANK
|
||
#define FBIOBLANK 0x4611
|
||
#endif
|
||
#define BACKLIGHT "/sys/class/backlight/backlight_pwm0/brightness"
|
||
#define BACKLIGHT_POWER "/sys/class/backlight/backlight_pwm0/bl_power"
|
||
|
||
/* ---- 热键切换(HibyOS→MeowOS):按住电源 + 窗口内单击音量减 ---- */
|
||
#define HK_PWRWIN_MIN 60 /* 电源按下后 60ms 起 */
|
||
#define HK_PWRWIN_MAX 2500 /* 至 2500ms 止 */
|
||
#define KILL_GRACE_MS 3000 /* SIGTERM 后等待退出,超时 SIGKILL */
|
||
#define ARM_DELAY_MS 20000 /* 开机武装延迟(§18.23):播放器拉起后 N ms 内热键不触发,避媒体扫描期冻结 */
|
||
#define BUSY_STATE_CHAR 'D' /* 播放器忙判门:/proc/<pid>/stat 态为 D(不可中断 IO=扫描)则暂不热键 */
|
||
#define SCAN_KEEP_MS 90000 /* Bug 4:HibyOS 开机媒体扫描窗(90s)内亮度保底,防黑屏几分钟(只写亮度不碰 blank) */
|
||
|
||
/* ---- key codes ---- */
|
||
#ifndef KEY_POWER
|
||
#define KEY_POWER 116
|
||
#endif
|
||
#ifndef KEY_VOLUMEDOWN
|
||
#define KEY_VOLUMEDOWN 114
|
||
#endif
|
||
#ifndef KEY_VOLUMEUP
|
||
#define KEY_VOLUMEUP 115
|
||
#endif
|
||
#ifndef KEY_PLAYPAUSE
|
||
#define KEY_PLAYPAUSE 164
|
||
#endif
|
||
|
||
/* ---- 事件号 / ioctl ---- */
|
||
#define BTN_TOUCH 0x14a
|
||
#define ABS_X 0x00
|
||
#define ABS_Y 0x01
|
||
#define ABS_MT_POSITION_X 0x35 /* goodix 走 MT 协议 B,仅 ABS_X/Y 收不到坐标(M2.1 bug) */
|
||
#define ABS_MT_POSITION_Y 0x36
|
||
#ifndef EV_KEY
|
||
#define EV_KEY 1
|
||
#endif
|
||
#ifndef EV_ABS
|
||
#define EV_ABS 3
|
||
#endif
|
||
#ifndef EVIOCGKEY
|
||
#define EVIOCGKEY(len) _IOC(_IOC_READ, 'E', 0x18, len)
|
||
#endif
|
||
#define KEYBUF_WORDS 32 /* 1024 bits, 覆盖 KEY_MAX 767 */
|
||
|
||
/* ---- 编译默认值(可被 /usr/data/mnt/sd_0/MeowAPPs/meow.conf 覆写)---- */
|
||
#define DEF_BOOT_DEFAULT 1 /* 1=HibyOS 0=MeowOS */
|
||
#define DEF_BOOT_TIMEOUT_MS 5000
|
||
#define DEF_BOOT_MENU 1
|
||
#define DEF_BG 0x000000
|
||
|
||
#define POLL_MS 10
|
||
#define SIM_SELECT_DELAY_MS 2000 /* --sim-select 延迟后模拟选 */
|
||
#define GHOST_COOLDOWN_MS 800 /* 入口 drain + 短暂冷却防幽灵触摸(M2.1:缩短,不吞首次有效输入) */
|
||
#define MEOW_BOOTMENU_VER "0.1.0-m2.29-takeover" /* M2.29:supervisor 接管关机(poweroff)/升级(reboot) */
|
||
|
||
/* ---- 版式 ---- */
|
||
#define STATUS_H 40
|
||
#define ROW_H 52
|
||
#define ENTRY0_Y 176
|
||
#define ENTRY1_Y 228
|
||
#define FOOTER_Y 700
|
||
#define BOOTMENU_OS_VER_FILE "/etc/meowos_version" /* 版本号单点:与 launcher 同源读 /etc/meowos_version */
|
||
#define BOOTMENU_OS_VER "dev" /* 兜底:文件缺失/读失败时用(与 launcher MEOWOS_VERSION=dev 对齐) */
|
||
|
||
/* 右下角显示的 MeowOS 版本:启动时读 /etc/meowos_version(与 launcher 同一数据源),
|
||
* 不再硬编码 0.7.x。读到的值存 g_boot_ver;缺失/读失败保留兜底默认。 */
|
||
static char g_boot_ver[32] = BOOTMENU_OS_VER;
|
||
static void load_boot_ver(void)
|
||
{
|
||
FILE *f = fopen(BOOTMENU_OS_VER_FILE, "r");
|
||
if (!f) return; /* 缺失:保留兜底 default */
|
||
char buf[32];
|
||
if (fgets(buf, sizeof buf, f)) {
|
||
char *e = buf + strlen(buf);
|
||
while (e > buf && (e[-1] == '\n' || e[-1] == '\r' || e[-1] == ' ')) e--;
|
||
*e = 0;
|
||
if (buf[0]) snprintf(g_boot_ver, sizeof g_boot_ver, "%s", buf);
|
||
}
|
||
fclose(f);
|
||
}
|
||
|
||
/* ================================================================== */
|
||
/* fb0 状态 */
|
||
/* ================================================================== */
|
||
static unsigned char *fb = NULL;
|
||
static int fb_fd = -1;
|
||
static int fb_xres = 480, fb_yres = 720, fb_line = 960, fb_bpp = 16;
|
||
static unsigned int fb_yres_virtual = 0;
|
||
static unsigned int fb_active_yoffset = 0;
|
||
static unsigned char *fb_snapshot = NULL; /* 进入选单前快照(恢复播放器帧用) */
|
||
static size_t fb_snapshot_len = 0;
|
||
static int fb_orig_yoffset = -1;
|
||
|
||
/* 触摸状态(选单/确认框/--dumpkeys 共用;M2.26 迁到 meow_touch 统一接口:
|
||
* goodix 时序免疫——BTN_TOUCH=1 不锁原点,等按下后首个有效 ABS 才锁,位移>=65 判滑动)。 */
|
||
static meow_touch mt_touch;
|
||
static int touch_fd = -1;
|
||
|
||
static inline void put_px(int x, int y, uint32_t color)
|
||
{
|
||
if (x < 0 || x >= fb_xres || y < 0 || y >= fb_yres) return;
|
||
unsigned char *p = fb + (size_t)y * fb_line + x * (fb_bpp / 8);
|
||
if (fb_bpp == 16) {
|
||
uint16_t c = ((color >> 8) & 0xF800) | ((color >> 5) & 0x07E0) |
|
||
((color >> 3) & 0x001F);
|
||
*(uint16_t *)p = c;
|
||
} else {
|
||
*(uint32_t *)p = (color << 8) | 0xFF;
|
||
}
|
||
}
|
||
|
||
static void fill_rect(int x0, int y0, int w, int h, uint32_t color)
|
||
{
|
||
for (int y = y0; y < y0 + h; y++)
|
||
for (int x = x0; x < x0 + w; x++)
|
||
put_px(x, y, color);
|
||
}
|
||
|
||
/* 5x7 点阵(与 meow_launcherd/hello 相同;仅 ASCII) */
|
||
static const unsigned char font[][8] = {
|
||
{0x0E,0x11,0x13,0x15,0x19,0x11,0x0E,0x00},
|
||
{0x04,0x0C,0x04,0x04,0x04,0x04,0x0E,0x00},
|
||
{0x0E,0x11,0x01,0x02,0x04,0x08,0x1F,0x00},
|
||
{0x1F,0x02,0x04,0x02,0x01,0x11,0x0E,0x00},
|
||
{0x02,0x06,0x0A,0x12,0x1F,0x02,0x02,0x00},
|
||
{0x1F,0x10,0x1E,0x01,0x01,0x11,0x0E,0x00},
|
||
{0x06,0x08,0x10,0x1E,0x11,0x11,0x0E,0x00},
|
||
{0x1F,0x01,0x02,0x04,0x08,0x08,0x08,0x00},
|
||
{0x0E,0x11,0x11,0x0E,0x11,0x11,0x0E,0x00},
|
||
{0x0E,0x11,0x11,0x0F,0x01,0x02,0x0C,0x00},
|
||
{0x0E,0x11,0x11,0x1F,0x11,0x11,0x11,0x00},
|
||
{0x1E,0x11,0x11,0x1E,0x11,0x11,0x1E,0x00},
|
||
{0x0E,0x11,0x10,0x10,0x10,0x11,0x0E,0x00},
|
||
{0x1C,0x12,0x11,0x11,0x11,0x12,0x1C,0x00},
|
||
{0x1F,0x10,0x10,0x1E,0x10,0x10,0x1F,0x00},
|
||
{0x1F,0x10,0x10,0x1E,0x10,0x10,0x10,0x00},
|
||
{0x0E,0x11,0x10,0x17,0x11,0x11,0x0F,0x00},
|
||
{0x11,0x11,0x11,0x1F,0x11,0x11,0x11,0x00},
|
||
{0x0E,0x04,0x04,0x04,0x04,0x04,0x0E,0x00},
|
||
{0x07,0x02,0x02,0x02,0x02,0x12,0x0C,0x00},
|
||
{0x11,0x12,0x14,0x18,0x14,0x12,0x11,0x00},
|
||
{0x10,0x10,0x10,0x10,0x10,0x10,0x1F,0x00},
|
||
{0x11,0x1B,0x15,0x15,0x11,0x11,0x11,0x00},
|
||
{0x11,0x19,0x15,0x13,0x11,0x11,0x11,0x00},
|
||
{0x0E,0x11,0x11,0x11,0x11,0x11,0x0E,0x00},
|
||
{0x1E,0x11,0x11,0x1E,0x10,0x10,0x10,0x00},
|
||
{0x0E,0x11,0x11,0x11,0x15,0x12,0x0D,0x00},
|
||
{0x1E,0x11,0x11,0x1E,0x14,0x12,0x11,0x00},
|
||
{0x0F,0x10,0x10,0x0E,0x01,0x01,0x1E,0x00},
|
||
{0x1F,0x04,0x04,0x04,0x04,0x04,0x04,0x00},
|
||
{0x11,0x11,0x11,0x11,0x11,0x11,0x0E,0x00},
|
||
{0x11,0x11,0x11,0x11,0x11,0x0A,0x04,0x00},
|
||
{0x11,0x11,0x11,0x15,0x15,0x1B,0x11,0x00},
|
||
{0x11,0x11,0x0A,0x04,0x0A,0x11,0x11,0x00},
|
||
{0x11,0x11,0x11,0x0A,0x04,0x04,0x04,0x00},
|
||
{0x1F,0x01,0x02,0x04,0x08,0x10,0x1F,0x00},
|
||
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
|
||
{0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x00},
|
||
{0x04,0x04,0x04,0x04,0x04,0x00,0x04,0x00},
|
||
{0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x00},
|
||
{0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x00},
|
||
};
|
||
|
||
static int glyph_index(char c)
|
||
{
|
||
if (c >= '0' && c <= '9') return c - '0';
|
||
if (c >= 'A' && c <= 'Z') return 10 + (c - 'A');
|
||
if (c >= 'a' && c <= 'z') return 10 + (c - 'a');
|
||
if (c == ' ') return 36;
|
||
if (c == '.') return 37;
|
||
if (c == '!') return 38;
|
||
if (c == '-' || c == '_') return 39;
|
||
return 36;
|
||
}
|
||
|
||
static void draw_glyph(int x, int y, char c, int scale, uint32_t color)
|
||
{
|
||
const unsigned char *g = font[glyph_index(c)];
|
||
for (int row = 0; row < 7; row++) {
|
||
unsigned char bits = g[row];
|
||
for (int col = 0; col < 5; col++) {
|
||
if (bits & (0x10 >> col)) {
|
||
for (int dy = 0; dy < scale; dy++)
|
||
for (int dx = 0; dx < scale; dx++)
|
||
put_px(x + col * scale + dx, y + row * scale + dy, color);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
static void draw_text(int x, int y, const char *s, int scale, uint32_t color)
|
||
{
|
||
while (*s) {
|
||
draw_glyph(x, y, *s, scale, color);
|
||
x += 6 * scale;
|
||
s++;
|
||
}
|
||
}
|
||
|
||
/* ================================================================== */
|
||
/* fb0 初始化 / 双缓冲 pan(§18.18) */
|
||
/* ================================================================== */
|
||
static int fb_open(void)
|
||
{
|
||
int fd = open(FB_DEV, O_RDWR);
|
||
if (fd < 0) { fprintf(stderr, "fb open failed: %s\n", strerror(errno)); return -1; }
|
||
unsigned char vinfo[160], finfo[68];
|
||
memset(vinfo, 0, sizeof vinfo);
|
||
memset(finfo, 0, sizeof finfo);
|
||
if (ioctl(fd, FBIOGET_VSCREENINFO, vinfo) < 0 ||
|
||
ioctl(fd, FBIOGET_FSCREENINFO, finfo) < 0) {
|
||
fprintf(stderr, "fb ioctl failed\n");
|
||
close(fd); return -1;
|
||
}
|
||
fb_xres = *(uint32_t *)(vinfo + 0);
|
||
fb_yres = *(uint32_t *)(vinfo + 4);
|
||
fb_yres_virtual = *(uint32_t *)(vinfo + 12);
|
||
fb_active_yoffset = *(uint32_t *)(vinfo + 20);
|
||
fb_bpp = *(uint32_t *)(vinfo + 24);
|
||
fb_line = *(uint32_t *)(finfo + 44);
|
||
if (!fb_line) fb_line = fb_xres * (fb_bpp / 8);
|
||
if (fb_yres_virtual < fb_yres) fb_yres_virtual = fb_yres; /* 单缓冲兜底 */
|
||
fb = mmap(NULL, (size_t)fb_line * fb_yres_virtual,
|
||
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||
if (fb == MAP_FAILED) {
|
||
fprintf(stderr, "fb mmap failed: %s\n", strerror(errno));
|
||
close(fd); return -1;
|
||
}
|
||
fb_fd = fd;
|
||
fprintf(stderr, "fb: %dx%d bpp=%d line=%d vres=%u yoffset=%u\n",
|
||
fb_xres, fb_yres, fb_bpp, fb_line, fb_yres_virtual, fb_active_yoffset);
|
||
return 0;
|
||
}
|
||
|
||
/* 选单进入前快照「当前显示缓冲」一帧(播放器事件驱动不主动重绘,须恢复) */
|
||
static void fb_snapshot_player(void)
|
||
{
|
||
if (fb_yres_virtual <= fb_yres) { fb_orig_yoffset = -1; return; }
|
||
if (fb_active_yoffset >= fb_yres_virtual) fb_active_yoffset = 0;
|
||
fb_orig_yoffset = (int)fb_active_yoffset;
|
||
fb_snapshot_len = (size_t)fb_line * fb_yres;
|
||
fb_snapshot = malloc(fb_snapshot_len);
|
||
if (!fb_snapshot) { fb_orig_yoffset = -1; fb_snapshot_len = 0; return; }
|
||
memcpy(fb_snapshot, fb + (size_t)fb_active_yoffset * fb_line, fb_snapshot_len);
|
||
}
|
||
|
||
/* 画前 FBIOPAN_DISPLAY pan 到 yoffset=0(buffer0,选单/app 共同绘制目标) */
|
||
static void fb_pan_buf0(void)
|
||
{
|
||
if (fb_fd < 0 || fb_yres_virtual <= fb_yres) { fb_active_yoffset = 0; return; }
|
||
if (fb_active_yoffset == 0) return; /* 已显示 buffer0 */
|
||
unsigned char v[160];
|
||
memset(v, 0, sizeof v);
|
||
if (ioctl(fb_fd, FBIOGET_VSCREENINFO, v) < 0) return;
|
||
*(uint32_t *)(v + 16) = 0; /* xoffset */
|
||
*(uint32_t *)(v + 20) = 0; /* yoffset -> buffer0 */
|
||
ioctl(fb_fd, FBIOPAN_DISPLAY, v);
|
||
fb_active_yoffset = 0;
|
||
}
|
||
|
||
/* 选单退出:把快照拷回原缓冲并 pan 回原 yoffset(播放器帧保持不动) */
|
||
static void fb_restore_player_frame(void)
|
||
{
|
||
int restore_y = fb_orig_yoffset;
|
||
if (fb_fd >= 0 && restore_y >= 0 && fb_snapshot) {
|
||
memcpy(fb + (size_t)restore_y * fb_line, fb_snapshot, fb_snapshot_len);
|
||
unsigned char v[160];
|
||
memset(v, 0, sizeof v);
|
||
if (ioctl(fb_fd, FBIOGET_VSCREENINFO, v) == 0) {
|
||
*(uint32_t *)(v + 16) = 0;
|
||
*(uint32_t *)(v + 20) = (uint32_t)restore_y;
|
||
ioctl(fb_fd, FBIOPAN_DISPLAY, v);
|
||
}
|
||
fprintf(stderr, "bootmenu: fb restore -> yoffset=%d\n", restore_y);
|
||
}
|
||
if (fb_snapshot) { free(fb_snapshot); fb_snapshot = NULL; }
|
||
fb_orig_yoffset = -1;
|
||
fb_snapshot_len = 0;
|
||
}
|
||
|
||
static void fb_close(void)
|
||
{
|
||
if (fb && fb != MAP_FAILED)
|
||
munmap(fb, (size_t)fb_line * fb_yres_virtual);
|
||
if (fb_fd >= 0) close(fb_fd);
|
||
fb = NULL; fb_fd = -1;
|
||
}
|
||
|
||
/* ================================================================== */
|
||
/* 状态栏数据源(与 meow_launcherd 相同:电量/WIFI/BT/音量) */
|
||
/* ================================================================== */
|
||
static int read_file_int(const char *path)
|
||
{
|
||
int fd = open(path, O_RDONLY);
|
||
if (fd < 0) return -1;
|
||
char buf[16];
|
||
int n = read(fd, buf, sizeof buf - 1);
|
||
close(fd);
|
||
if (n <= 0) return -1;
|
||
buf[n] = 0;
|
||
return atoi(buf);
|
||
}
|
||
|
||
static int bluetooth_present(void)
|
||
{
|
||
return access("/sys/class/bluetooth/hci0", F_OK) == 0;
|
||
}
|
||
|
||
/* 音量:CS43198 ALSA control(实测死镜像,可能恒 0%,见 §18.13) */
|
||
static int amixer_vol(int numid)
|
||
{
|
||
char cmd[96];
|
||
snprintf(cmd, sizeof cmd, "amixer -c 0 cget numid=%d 2>/dev/null", numid);
|
||
FILE *p = popen(cmd, "r");
|
||
if (!p) return -1;
|
||
char buf[128];
|
||
int v = -1;
|
||
while (fgets(buf, sizeof buf, p)) {
|
||
char *s = strstr(buf, ": values=");
|
||
if (s) { v = atoi(s + 9); break; }
|
||
}
|
||
pclose(p);
|
||
return v;
|
||
}
|
||
|
||
/* 28px 状态栏:行1=时钟(左) 电量% WIFI BT(右),行2=VOL nn%(左),y=28 分隔线(40→28,用户定) */
|
||
static void draw_statusbar(uint32_t bg)
|
||
{
|
||
char timebuf[16], right[48];
|
||
time_t now = time(NULL);
|
||
struct tm tm;
|
||
localtime_r(&now, &tm);
|
||
snprintf(timebuf, sizeof timebuf, "%02d:%02d:%02d",
|
||
tm.tm_hour, tm.tm_min, tm.tm_sec);
|
||
right[0] = 0;
|
||
|
||
char batt[8] = "";
|
||
int b = read_file_int("/sys/class/power_supply/battery/capacity");
|
||
if (b >= 0) snprintf(batt, sizeof batt, "%d%%", b);
|
||
|
||
char wifi[8] = "";
|
||
int wf = open("/sys/class/net/wlan0/operstate", O_RDONLY);
|
||
if (wf >= 0) {
|
||
char x[8];
|
||
int n = read(wf, x, sizeof x - 1);
|
||
close(wf);
|
||
if (n > 0) { x[n] = 0; if (!strncmp(x, "up", 2)) snprintf(wifi, sizeof wifi, "WIFI"); }
|
||
}
|
||
|
||
char vol[16] = "";
|
||
int v = amixer_vol(1);
|
||
if (v >= 0) snprintf(vol, sizeof vol, "VOL %d%%", v * 100 / 255);
|
||
|
||
char bt[8] = "";
|
||
if (bluetooth_present()) snprintf(bt, sizeof bt, "BT");
|
||
|
||
draw_text(8, 2, timebuf, 1, 0x9FB8D9);
|
||
if (batt[0]) strcat(right, batt);
|
||
if (wifi[0]) { if (right[0]) strcat(right, " "); strcat(right, wifi); }
|
||
if (bt[0]) { if (right[0]) strcat(right, " "); strcat(right, bt); }
|
||
if (right[0]) {
|
||
int rx = fb_xres - 8 - (int)strlen(right) * 6;
|
||
draw_text(rx, 2, right, 1, 0x9FB8D9);
|
||
}
|
||
if (vol[0]) draw_text(8, 14, vol, 1, 0x9FB8D9);
|
||
fill_rect(0, 28, fb_xres, 1, 0x333355); /* 分隔线(移至 y=28) */
|
||
}
|
||
|
||
/* 只刷时钟数字(每秒一次,不弹 amixer/不碰菜单体) */
|
||
static void draw_clock(uint32_t bg)
|
||
{
|
||
char timebuf[16];
|
||
time_t now = time(NULL);
|
||
struct tm tm;
|
||
localtime_r(&now, &tm);
|
||
snprintf(timebuf, sizeof timebuf, "%02d:%02d:%02d",
|
||
tm.tm_hour, tm.tm_min, tm.tm_sec);
|
||
int w = (int)strlen(timebuf) * 6;
|
||
fill_rect(4, 0, 8 + w, 12, bg);
|
||
draw_text(8, 2, timebuf, 1, 0x9FB8D9);
|
||
}
|
||
|
||
/* ================================================================== */
|
||
/* 配置:boot_default / boot_timeout_ms / boot_menu / bg_color */
|
||
/* boot_default:权威 = 板载 /data/meow_boot_default(O_TRUNC 单值、不写 TF 卡);
|
||
* meow.conf 的 boot_default= 仅作无板载标记时的兜底(不新增写入,见 set_boot_default_meow)。 */
|
||
/* ================================================================== */
|
||
static int cfg_boot_default = DEF_BOOT_DEFAULT;
|
||
static long cfg_boot_timeout_ms = DEF_BOOT_TIMEOUT_MS;
|
||
static int cfg_boot_menu = DEF_BOOT_MENU;
|
||
static uint32_t cfg_bg = DEF_BG;
|
||
|
||
/* 读板载 /data/meow_boot_default(开机默认权威标记)。返回 0/1;-1=不存在/非法。 */
|
||
static int read_boot_default_onboard(void)
|
||
{
|
||
int fd = open("/data/meow_boot_default", O_RDONLY);
|
||
if (fd < 0) return -1;
|
||
char buf[8];
|
||
int n = read(fd, buf, sizeof buf - 1);
|
||
close(fd);
|
||
if (n <= 0) return -1;
|
||
buf[n] = 0;
|
||
int v = atoi(buf);
|
||
return (v == 0 || v == 1) ? v : -1;
|
||
}
|
||
|
||
static void load_config(void)
|
||
{
|
||
cfg_boot_default = DEF_BOOT_DEFAULT;
|
||
cfg_boot_timeout_ms = DEF_BOOT_TIMEOUT_MS;
|
||
cfg_boot_menu = DEF_BOOT_MENU;
|
||
cfg_bg = DEF_BG;
|
||
int has_conf = 1;
|
||
FILE *f = fopen(CONF_FILE, "r");
|
||
if (!f) { fprintf(stderr, "bootmenu: no %s, using defaults\n", CONF_FILE); has_conf = 0; }
|
||
if (has_conf) {
|
||
char buf[128];
|
||
while (fgets(buf, sizeof buf, f)) {
|
||
if (!strncmp(buf, "boot_default=", 13)) {
|
||
/* 板载 /data/meow_boot_default 为权威;此处仅作无板载标记时的兜底(不再新增写入)。 */
|
||
cfg_boot_default = atoi(buf + 13);
|
||
if (cfg_boot_default != 0 && cfg_boot_default != 1) cfg_boot_default = DEF_BOOT_DEFAULT;
|
||
} else if (!strncmp(buf, "boot_timeout_ms=", 16)) {
|
||
long t = atol(buf + 16);
|
||
if (t >= 500 && t <= 60000) cfg_boot_timeout_ms = t;
|
||
} else if (!strncmp(buf, "boot_menu=", 10)) {
|
||
int m = atoi(buf + 10);
|
||
if (m == 0 || m == 1) cfg_boot_menu = m;
|
||
} else if (!strncmp(buf, "bg_color=", 9)) {
|
||
unsigned int v;
|
||
if (sscanf(buf + 9, "%x", &v) == 1) cfg_bg = v & 0xFFFFFF;
|
||
}
|
||
}
|
||
fclose(f);
|
||
}
|
||
/* 板载标记优先(权威):覆盖 meow.conf 兜底/编译默认。 */
|
||
int ob = read_boot_default_onboard();
|
||
if (ob == 0 || ob == 1) {
|
||
if (ob != cfg_boot_default)
|
||
fprintf(stderr, "bootmenu: boot_default onboard=%d (override %d)\n", ob, cfg_boot_default);
|
||
cfg_boot_default = ob;
|
||
}
|
||
fprintf(stderr, "bootmenu: cfg boot_default=%d timeout_ms=%ld boot_menu=%d bg=%06x\n",
|
||
cfg_boot_default, cfg_boot_timeout_ms, cfg_boot_menu, cfg_bg);
|
||
}
|
||
|
||
/* ================================================================== */
|
||
/* 时钟(单调,强收敛) */
|
||
/* ================================================================== */
|
||
static unsigned long long now_ms(void)
|
||
{
|
||
struct timespec ts;
|
||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||
return (unsigned long long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||
}
|
||
|
||
/* ================================================================== */
|
||
/* TF 卡日志(bm_log)——诊断留存:supervisor 收到/发出信号、播放器退出原因。
|
||
* - 借鉴 launcher log_open_card/logmsg 思路,但**不共享 /tmp/meow_bootts**
|
||
* (launcher 缓存的是完整基名 meow_<ts>,直接复读会拼成 meow_boot_meow_…);
|
||
* 每次自行按 /proc/uptime 推开机时间戳,同一 boot 内文件名恒定。
|
||
* - 写 /usr/data/mnt/sd_0/MeowOS/logs/meow_boot_<bootts>.log(O_APPEND|O_CREAT 0644)。
|
||
* - 卡不可用(未挂载/不可写)→ 降级 stderr。**只记录,不改任何行为。**
|
||
* ================================================================== */
|
||
#define BM_LOG_DIR "/usr/data/mnt/sd_0/MeowOS/logs"
|
||
static int bm_log_fd = -1;
|
||
static char bm_log_file[256] = "";
|
||
|
||
static void bm_boot_ts(char *out, size_t n)
|
||
{
|
||
time_t now = time(NULL), up = 0;
|
||
int fd = open("/proc/uptime", O_RDONLY);
|
||
if (fd >= 0) {
|
||
char buf[64];
|
||
int r = read(fd, buf, sizeof buf - 1);
|
||
close(fd);
|
||
if (r > 0) {
|
||
buf[r] = 0;
|
||
char *p = strchr(buf, ' ');
|
||
if (p) *p = 0;
|
||
up = (time_t)atof(buf);
|
||
}
|
||
}
|
||
time_t boot = (time_t)((double)now - (double)up + 0.5);
|
||
struct tm tm;
|
||
localtime_r(&boot, &tm);
|
||
strftime(out, n, "%Y%m%d_%H%M%S", &tm);
|
||
}
|
||
|
||
static void bm_log_open(void)
|
||
{
|
||
if (bm_log_fd >= 0) return;
|
||
mkdir(BM_LOG_DIR, 0755);
|
||
char ts[64];
|
||
bm_boot_ts(ts, sizeof ts);
|
||
snprintf(bm_log_file, sizeof bm_log_file, "%s/meow_boot_%s.log", BM_LOG_DIR, ts);
|
||
bm_log_fd = open(bm_log_file, O_WRONLY | O_APPEND | O_CREAT, 0644);
|
||
if (bm_log_fd < 0) bm_log_file[0] = 0; /* 卡不可用 → 降级 stderr */
|
||
}
|
||
|
||
/* 带时间戳写 TF 卡日志;卡不可用则降级 stderr。仅记录,不改行为。 */
|
||
static void bm_log(const char *fmt, ...)
|
||
{
|
||
char buf[512];
|
||
va_list ap;
|
||
va_start(ap, fmt);
|
||
vsnprintf(buf, sizeof buf, fmt, ap);
|
||
va_end(ap);
|
||
char line[576];
|
||
int l = snprintf(line, sizeof line, "%llu %s\n", now_ms(), buf);
|
||
if (bm_log_fd < 0) bm_log_open();
|
||
if (bm_log_fd >= 0) {
|
||
if (write(bm_log_fd, line, l) < 0) bm_log_fd = -1; /* 卡拔出 → 回退 stderr */
|
||
}
|
||
fprintf(stderr, "%llu %s\n", now_ms(), buf);
|
||
}
|
||
|
||
/* ---- supervisor 信号记录(只记录,不改行为)----
|
||
* 注册 SIGTERM/SIGINT/SIGQUIT/SIGHUP,handler 只置标志;主循环顶部先 bm_log
|
||
* 「收到信号 N」,再 signal(s, SIG_DFL) + raise(s) 重抛——**恢复默认动作**,
|
||
* 使 supervisor 仍按今日方式终止(不转发、不善后、不截断,与未加 hook 一致)。 */
|
||
static volatile sig_atomic_t bm_sig_flag[64];
|
||
|
||
static const char *bm_sig_name(int sig)
|
||
{
|
||
switch (sig) {
|
||
case SIGTERM: return "SIGTERM";
|
||
case SIGKILL: return "SIGKILL";
|
||
case SIGSTOP: return "SIGSTOP";
|
||
case SIGCONT: return "SIGCONT";
|
||
case SIGINT: return "SIGINT";
|
||
case SIGQUIT: return "SIGQUIT";
|
||
case SIGHUP: return "SIGHUP";
|
||
default: return "?";
|
||
}
|
||
}
|
||
|
||
static void bm_sig_catch(int sig)
|
||
{
|
||
if (sig > 0 && sig < 64) bm_sig_flag[sig] = 1;
|
||
}
|
||
|
||
static void bm_sig_install(void)
|
||
{
|
||
signal(SIGTERM, bm_sig_catch);
|
||
signal(SIGINT, bm_sig_catch);
|
||
signal(SIGQUIT, bm_sig_catch);
|
||
signal(SIGHUP, bm_sig_catch);
|
||
}
|
||
|
||
/* 主循环顶部调用:记录已收到的信号,并恢复默认动作重抛(保行为不变)。 */
|
||
static void bm_sig_poll(void)
|
||
{
|
||
for (int s = 1; s < 64; s++) {
|
||
if (bm_sig_flag[s]) {
|
||
bm_sig_flag[s] = 0;
|
||
bm_log("supervisor: received signal %d (%s)", s, bm_sig_name(s));
|
||
signal(s, SIG_DFL); /* 恢复默认动作(== 未加 hook 时) */
|
||
raise(s); /* 重抛:已登记即按原方式终止 */
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* ---- M2.29 supervisor 接管:直连 reboot(2) syscall + mtd5(ota) 侦测 ----
|
||
* 借鉴 meow_launcherd sys_do_reboot(BusyBox reboot 非 PID1 不完成握手,故直接 syscall)。
|
||
* sync() 落盘后调用 reboot(2),内核立即重启/关机;正常不返回,防御性死循环。 */
|
||
#define LINUX_REBOOT_MAGIC1 0xfee1dead
|
||
#define LINUX_REBOOT_MAGIC2 672274793 /* 0x28121969 */
|
||
#define LINUX_REBOOT_CMD_RESTART 0x01234567
|
||
#define LINUX_REBOOT_CMD_POWER_OFF 0x4321fedc
|
||
static void bm_do_reboot(int cmd)
|
||
{
|
||
sync();
|
||
bm_log("supervisor: bm_do_reboot cmd=0x%x", cmd);
|
||
syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, cmd, NULL);
|
||
bm_log("supervisor: bm_do_reboot returned: %s", strerror(errno));
|
||
for (;;) usleep(1000); /* 防御:永不返回 */
|
||
}
|
||
|
||
/* mtd5 是 ota 标记分区(8M)。bootmode.sh Recovery 用 nandwrite 在 offset 0 写 256B
|
||
* 定长标记。这里每 500ms 读一次 mtd5 头 ~15B(单页一小读,IO 极低),命中
|
||
* "ota:kernel2" 即判「播放器触发升级」。open/close 每次都关,避免长期占 mtd 设备。 */
|
||
#define MTD_OTA "/dev/mtd5"
|
||
static int mtd_ota_is_upgrade(void)
|
||
{
|
||
int fd = open(MTD_OTA, O_RDONLY);
|
||
if (fd < 0) return 0; /* 读不到 → 无升级迹象 */
|
||
char buf[16];
|
||
memset(buf, 0, sizeof buf);
|
||
ssize_t n = read(fd, buf, sizeof buf - 1);
|
||
close(fd);
|
||
if (n <= 0) return 0;
|
||
return strstr(buf, "ota:kernel2") != NULL;
|
||
}
|
||
|
||
/* ================================================================== */
|
||
/* 按键状态轮询(EVIOCGKEY) */
|
||
/* ================================================================== */
|
||
struct keypoll {
|
||
int fd;
|
||
unsigned long cur[KEYBUF_WORDS];
|
||
unsigned long prev[KEYBUF_WORDS];
|
||
int ok;
|
||
};
|
||
|
||
static int kp_open(struct keypoll *kp, const char *dev)
|
||
{
|
||
memset(kp, 0, sizeof *kp);
|
||
kp->fd = open(dev, O_RDONLY | O_NONBLOCK);
|
||
if (kp->fd < 0) { fprintf(stderr, "open %s: %s\n", dev, strerror(errno)); return -1; }
|
||
if (ioctl(kp->fd, EVIOCGKEY(sizeof kp->cur), kp->cur) < 0) {
|
||
fprintf(stderr, "EVIOCGKEY %s: %s\n", dev, strerror(errno));
|
||
close(kp->fd); return -1;
|
||
}
|
||
memcpy(kp->prev, kp->cur, sizeof kp->cur);
|
||
kp->ok = 1;
|
||
return 0;
|
||
}
|
||
|
||
/* 轮询设备键状态;对每个「按下边沿/抬起边沿」调 cb(code, down, ctx)。
|
||
* 返回是否有边沿。 */
|
||
static int kp_poll(struct keypoll *kp, int (*cb)(int code, int down, void *ctx),
|
||
void *ctx)
|
||
{
|
||
if (!kp->ok) return 0;
|
||
if (ioctl(kp->fd, EVIOCGKEY(sizeof kp->cur), kp->cur) < 0) return 0;
|
||
int changed = 0;
|
||
for (int w = 0; w < KEYBUF_WORDS; w++) {
|
||
if (kp->cur[w] == kp->prev[w]) continue;
|
||
for (int b = 0; b < 32; b++) {
|
||
int c = (kp->cur[w] >> b) & 1;
|
||
int p = (kp->prev[w] >> b) & 1;
|
||
if (c != p) {
|
||
int code = w * 32 + b;
|
||
changed = 1;
|
||
if (cb) cb(code, c, ctx);
|
||
}
|
||
}
|
||
kp->prev[w] = kp->cur[w];
|
||
}
|
||
return changed;
|
||
}
|
||
|
||
static int kp_is_down(const struct keypoll *k, int code)
|
||
{
|
||
return (k->cur[code / 32] >> (code % 32)) & 1;
|
||
}
|
||
|
||
/* ================================================================== */
|
||
/* 选单绘制 */
|
||
/* ================================================================== */
|
||
static void draw_countdown(uint32_t bg, long remaining_ms)
|
||
{
|
||
/* 右侧倒计时 'AUTO N S'(scale2) */
|
||
int sec = (int)((remaining_ms + 999) / 1000);
|
||
char s[16];
|
||
snprintf(s, sizeof s, "AUTO %dS", sec);
|
||
int w = (int)strlen(s) * 12; /* scale2 = 12px/字 */
|
||
int x = fb_xres - 8 - w;
|
||
/* 清一下原区域(含右边距)再画 */
|
||
fill_rect(x - 4, 92, w + 12, 16, bg);
|
||
draw_text(x, 94, s, 2, 0xFFE066);
|
||
}
|
||
|
||
static void draw_entry(int y, const char *label, int selected)
|
||
{
|
||
if (selected) {
|
||
fill_rect(8, y - 4, fb_xres - 16, ROW_H - 8, 0x2A3A6A);
|
||
draw_text(24, y, label, 2, 0xFFFFFF);
|
||
} else {
|
||
draw_text(24, y, label, 2, 0xC8D0E8);
|
||
}
|
||
}
|
||
|
||
static void draw_menu(int cursor, long remaining_ms)
|
||
{
|
||
fill_rect(0, 0, fb_xres, fb_yres, cfg_bg);
|
||
|
||
/* 状态栏 + 标题 + 分隔线 */
|
||
draw_statusbar(cfg_bg);
|
||
draw_text(20, 64, "MEOWOS", 3, 0xFFE066);
|
||
draw_text(22, 96, "BOOT SELECTOR", 2, 0x9FB8D9);
|
||
fill_rect(0, 122, fb_xres, 2, 0x3A3A5C);
|
||
|
||
/* 两个入口 */
|
||
draw_entry(ENTRY0_Y, "[HIBYOS PLAYER]", cursor == 0);
|
||
draw_entry(ENTRY1_Y, "[MEOWOS HOME]", cursor == 1);
|
||
|
||
/* 右侧倒计时 */
|
||
draw_countdown(cfg_bg, remaining_ms);
|
||
|
||
/* 底部按键提示 */
|
||
draw_text(20, FOOTER_Y,
|
||
"VOLUP UP VOLDN DOWN PLAY OK POWER OK", 1, 0x666688);
|
||
/* 右下角开发版本 */
|
||
{
|
||
char ver[40];
|
||
snprintf(ver, sizeof ver, "MeowOS v%s boot", g_boot_ver);
|
||
draw_text(fb_xres - 8 - (int)strlen(ver) * 6, FOOTER_Y, ver, 1, 0x666688);
|
||
}
|
||
}
|
||
|
||
/* ================================================================== */
|
||
/* 面板/背光 helper(复用 meow_launcherd 语义,§18.21.5 / §红线) */
|
||
/* ================================================================== */
|
||
static int read_brightness(void)
|
||
{
|
||
int fd = open(BACKLIGHT, O_RDONLY);
|
||
if (fd < 0) return -1;
|
||
char buf[16];
|
||
int n = read(fd, buf, sizeof buf - 1);
|
||
close(fd);
|
||
if (n <= 0) return -1;
|
||
buf[n] = 0;
|
||
return atoi(buf);
|
||
}
|
||
|
||
static void write_file_str(const char *path, const char *val)
|
||
{
|
||
int fd = open(path, O_WRONLY);
|
||
if (fd < 0) return;
|
||
if (write(fd, val, strlen(val)) < 0)
|
||
fprintf(stderr, "bootmenu: write %s <- %s: %s\n", path, val, strerror(errno));
|
||
close(fd);
|
||
}
|
||
|
||
static void write_brightness(int val)
|
||
{
|
||
char buf[8];
|
||
int l = snprintf(buf, sizeof buf, "%d\n", val);
|
||
int fd = open(BACKLIGHT, O_WRONLY);
|
||
if (fd < 0) { fprintf(stderr, "bootmenu: backlight open fail: %s\n", strerror(errno)); return; }
|
||
if (write(fd, buf, l) < 0) fprintf(stderr, "bootmenu: backlight write %d: %s\n", val, strerror(errno));
|
||
close(fd);
|
||
}
|
||
|
||
/* 面板 unblank(FBIOBLANK 0)。仅用于杀播放器后把屏点亮,以便 MeowOS 接管;
|
||
* 播放器已死,故不涉及「播放器 state machine」一致性(§18.11 关注点)。
|
||
* bootmenu 选单/自检阶段绝不调用本函数(§18.24 纪律:不碰 panel blank)。 */
|
||
static void unblank_panel(void)
|
||
{
|
||
int fbfd = open(FB_DEV, O_RDWR);
|
||
if (fbfd >= 0) { ioctl(fbfd, FBIOBLANK, 0); close(fbfd); }
|
||
fprintf(stderr, "bootmenu: panel unblanked\n");
|
||
}
|
||
|
||
/* 读取 /proc/<pid>/stat 状态字符('S'/'T'/'D'...),失败返回 '\0'。 */
|
||
static char proc_state(int pid)
|
||
{
|
||
char path[64], line[512];
|
||
snprintf(path, sizeof path, "/proc/%d/stat", pid);
|
||
int fd = open(path, O_RDONLY);
|
||
if (fd < 0) return '\0';
|
||
ssize_t n = read(fd, line, sizeof line - 1);
|
||
close(fd);
|
||
if (n <= 0) return '\0';
|
||
line[n] = 0;
|
||
char *p = strrchr(line, ')');
|
||
if (!p || p + 2 >= line + n) return '\0';
|
||
return p[2];
|
||
}
|
||
|
||
/* 读取 /proc/<pid>/stat 状态是否为 T(stopped)。T 态 SIGTERM 不投递,
|
||
* 须先 SIGCONT(§18.21.3)。 */
|
||
static int proc_is_stopped(int pid)
|
||
{
|
||
return proc_state(pid) == 'T';
|
||
}
|
||
|
||
/* 播放器忙判门(§18.23):D(不可中断 IO = 开机媒体库扫描)视为忙。
|
||
* 忙期热键暂不触发,避免扫描期 SIGSTOP 冻结拉长卡顿。 */
|
||
static int proc_is_busy(int pid)
|
||
{
|
||
return proc_state(pid) == BUSY_STATE_CHAR;
|
||
}
|
||
|
||
/* ================================================================== */
|
||
/* 播放器 fork 契约(顺序:先 munmap+close fb0,再 fork) */
|
||
/* ⚠️ M0 记号:完整契约已按 BOOT_SELECTOR_DESIGN.md §3.6 实现,但仅在 */
|
||
/* 显式 --fork 时执行。M0 自检不传 --fork,故绝不 fork 正在运行的 */
|
||
/* hiby_player。M2 接管 HibyOS 时由 S90meow_bootmenu 传 --fork 开启。 */
|
||
/* ================================================================== */
|
||
extern char **environ;
|
||
|
||
static void write_mode(int choice); /* 定义在下方;supervisor 使用 */
|
||
static void bootmenu_supervisor(int player_pid); /* 定义在下方;fork 函数调用 */
|
||
|
||
/* ---- 真机 verif sim 钩子(M2):--sim-hotkey N / --sim-confirm Y|N ---- */
|
||
static long sim_hotkey_ms = 0; /* supervisor 启动后 N ms 模拟热键(电源按住+音量减) */
|
||
static int sim_confirm = 0; /* confirm 框打开后 2s 自动回答:'Y'=是 'N'=否 */
|
||
static int sim_confirm_done = 0;
|
||
static int sim_hk_done = 0;
|
||
static int sim_hotkey_strict = 0; /* M2.14:--sim-hotkey-strict = 让 sim 热键走武装延迟/忙判门(测 pending) */
|
||
|
||
/* ======================================================================
|
||
* M2.83 launcher 监管迁移:launcher 生命周期由 bootmenu 移交给 meow_sysui
|
||
* - MeowOS 模式:sysui 在 → 让 sysui 拉起/监管 launcher,bootmenu 让位不 exec
|
||
* (rcS 推进 → S92 见 mode=meow 跳过播放器);sysui 不在 → bootmenu 照旧 exec launcher(兜底)。
|
||
* - HibyOS 模式:sysui 在 → 让 sysui 停 launcher(会话级 user_stopped,不自动重启),避免与播放器抢 fb0。
|
||
* 双保险:sysui 拉 + launcher 崩 sysui 重启(restart=always);bootmenu 仅在 sysui 不在时兜底。
|
||
* ====================================================================== */
|
||
static int sysui_active(void)
|
||
{
|
||
/* sysui daemon 是否在跑:/tmp/meow_sysui.pid 存活,且 /proc/<pid>/comm=meow_sysui(防 pid 复用误判) */
|
||
FILE *f = fopen("/tmp/meow_sysui.pid", "r");
|
||
if (!f) return 0;
|
||
long pid = -1;
|
||
int n = fscanf(f, "%ld", &pid);
|
||
fclose(f);
|
||
if (n != 1 || pid <= 1) return 0;
|
||
if (kill((pid_t)pid, 0) != 0) return 0; /* ESRCH → 不在 */
|
||
char comm[64] = {0}, path[64];
|
||
snprintf(path, sizeof path, "/proc/%ld/comm", pid);
|
||
int fd = open(path, O_RDONLY);
|
||
if (fd >= 0) {
|
||
ssize_t r = read(fd, comm, sizeof comm - 1);
|
||
close(fd);
|
||
if (r > 0) {
|
||
char *nl = strchr(comm, '\n');
|
||
if (nl) *nl = '\0';
|
||
if (strncmp(comm, "meow_sysui", 10) != 0) return 0;
|
||
}
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
static void sysui_cmd_launcher(const char *op)
|
||
{
|
||
/* 调 /usr/bin/meow_sysui start|stop launcher(best-effort:sysui 缺失/失效 → 无害退出)。
|
||
* 等 CLI 返回(CLI 连 socket 快速回),避免僵尸;失败不致命。 */
|
||
pid_t pid = fork();
|
||
if (pid < 0) {
|
||
fprintf(stderr, "bootmenu: sysui %s launcher: fork failed: %s\n", op, strerror(errno));
|
||
return;
|
||
}
|
||
if (pid == 0) {
|
||
execl("/usr/bin/meow_sysui", "/usr/bin/meow_sysui", op, "launcher", (char *)NULL);
|
||
_exit(127); /* sysui 缺失/不可执行 → 无害退出(fallback 路径会用本地 exec) */
|
||
}
|
||
int st = 0;
|
||
(void)waitpid(pid, &st, 0);
|
||
}
|
||
|
||
static int enter_meow_launcher(void)
|
||
{
|
||
/* MeowOS 决策点:返回 0=已让位(launcher 由 sysui 继续管理,调用方正常退出/让 rcS 推进);
|
||
* 否则已 exec launcher(本函数不返回)。 */
|
||
if (sysui_active()) {
|
||
fprintf(stderr, "bootmenu: sysui owns launcher -> hand off (start via sysui)\n");
|
||
fflush(NULL);
|
||
sysui_cmd_launcher("start");
|
||
return 0;
|
||
}
|
||
/* 回退:sysui 不在 → bootmenu 照旧 exec launcher(防开不了机) */
|
||
fprintf(stderr, "bootmenu: no sysui -> exec %s (fallback)\n", LAUNCHER_BIN);
|
||
fflush(NULL);
|
||
execv(LAUNCHER_BIN, (char *[]){ "meow_launcherd", NULL });
|
||
fprintf(stderr, "bootmenu: exec launcher failed: %s\n", strerror(errno));
|
||
_exit(127);
|
||
}
|
||
|
||
static void fork_hiby_player(void)
|
||
{
|
||
/* M2.83 迁移:HibyOS 模式下 launcher 不应与播放器抢 fb0——sysui 在则让 sysui 停 launcher */
|
||
if (sysui_active()) {
|
||
fprintf(stderr, "bootmenu: HibyOS selected -> stop launcher via sysui\n");
|
||
fflush(NULL);
|
||
sysui_cmd_launcher("stop");
|
||
}
|
||
pid_t pid = fork();
|
||
if (pid < 0) { fprintf(stderr, "fork player failed: %s\n", strerror(errno)); return; }
|
||
if (pid == 0) {
|
||
/* child:完全复刻 init 拉起时的环境(§18.21.5 最小启动契约) */
|
||
chdir("/"); /* 必须:播放器有相对路径读(充电态等) */
|
||
clearenv();
|
||
setenv("USER", "root", 1);
|
||
setenv("SHLVL", "3", 1);
|
||
setenv("HOME", "/", 1);
|
||
setenv("PS1", "#", 1);
|
||
setenv("TERM", "vt102", 1);
|
||
setenv("PATH", "/bin:/sbin:/usr/bin:/usr/sbin", 1);
|
||
setenv("env_adb_device_name_prefix", "ingenic", 1);
|
||
setenv("env_adb_device_use_diffrent_name", "y", 1);
|
||
setenv("SHELL", "/bin/sh", 1);
|
||
setenv("PWD", "/", 1);
|
||
setenv("EDITOR", "/bin/vi", 1);
|
||
/* 无 LD_LIBRARY_PATH / 无 TZ(与开机一致) */
|
||
int dn = open("/dev/null", O_RDONLY);
|
||
if (dn >= 0) { dup2(dn, 0); close(dn); }
|
||
int lf = open(PLAYER_LOG, O_WRONLY | O_CREAT | O_APPEND, 0644);
|
||
if (lf >= 0) { dup2(lf, 1); dup2(lf, 2); close(lf); }
|
||
execve(PLAYER_BIN, (char *[]){ "hiby_player", NULL }, environ);
|
||
_exit(127);
|
||
}
|
||
/* parent(M2):退居监督者/热键守护——poll(10ms) 检测热键(按住电源 +
|
||
* 窗口 60-2500ms 内单击音量减)→ SIGTERM 播放器 → 3s 未退 SIGKILL
|
||
* (T 态先 SIGCONT)→ waitpid 收割 → unblank+亮度恢复 → 写
|
||
* /tmp/meow_boot_mode=meow → exec meow_launcherd(MeowOS 基础主屏)。
|
||
* 播放器异常退出(waitpid WNOHANG)→ 记日志,阶段 0 不自动重启(HGL)。
|
||
* 本函数在父进程永不返回(阻塞 rcS 直到整机重启或热键切换)。 */
|
||
bootmenu_supervisor(pid);
|
||
/* fork 失败已在上面 return;此处不可达(supervisor 永不返回),
|
||
* 仅防御:若 supervisor 异常返回则让 rcS 推进(S92 兜底)。 */
|
||
return;
|
||
}
|
||
|
||
/* ---- 热键确认框(M2 用户定案:热键后先确认,是才杀播放器)---- */
|
||
#define CONF_DEFAULT_CURSOR 1 /* 默认 NO(更安全:不误杀) */
|
||
#define CONF_COOLDOWN_MS 800 /* 防 goodix 幽灵触摸(§18.16);M2.1 缩短,不吞首次有效输入 */
|
||
#define CONF_ROW0_Y 280
|
||
#define CONF_ROW1_Y 330
|
||
#define CONF_ROW_H 44
|
||
#define CONF_ROW_LEFT 60
|
||
#define CONF_ROW_RIGHT (fb_xres - 60)
|
||
|
||
/* 简单绘制:压暗底 + 外框 + 标题 + [YES][NO] + 按键提示。
|
||
* 字体仅 ASCII(无 CJK),故用 ENTER MEOWOS? / STOP HIBYOS PLAYER。 */
|
||
static void draw_confirm_box(int cursor)
|
||
{
|
||
fill_rect(0, 0, fb_xres, fb_yres, 0x0D0D14); /* 压暗背景 */
|
||
fill_rect(36, 180, fb_xres - 72, 230, 0x3A3A5C); /* 外框 */
|
||
fill_rect(40, 184, fb_xres - 80, 222, 0x1A1A2A); /* 内底 */
|
||
draw_text(56, 200, "STOP HIBYOS PLAYER", 2, 0xFFE066);
|
||
draw_text(56, 232, "AND ENTER MEOWOS?", 2, 0x9FB8D9);
|
||
/* YES / NO 两行 */
|
||
if (cursor == 0) {
|
||
fill_rect(CONF_ROW_LEFT, CONF_ROW0_Y, CONF_ROW_RIGHT - CONF_ROW_LEFT, CONF_ROW_H, 0x2A3A6A);
|
||
draw_text(CONF_ROW_LEFT + 30, CONF_ROW0_Y + 12, "YES", 2, 0xFFFFFF);
|
||
} else {
|
||
draw_text(CONF_ROW_LEFT + 30, CONF_ROW0_Y + 12, "YES", 2, 0x667088);
|
||
}
|
||
if (cursor == 1) {
|
||
fill_rect(CONF_ROW_LEFT, CONF_ROW1_Y, CONF_ROW_RIGHT - CONF_ROW_LEFT, CONF_ROW_H, 0x2A3A6A);
|
||
draw_text(CONF_ROW_LEFT + 30, CONF_ROW1_Y + 12, "NO", 2, 0xFFFFFF);
|
||
} else {
|
||
draw_text(CONF_ROW_LEFT + 30, CONF_ROW1_Y + 12, "NO", 2, 0x667088);
|
||
}
|
||
draw_text(56, CONF_ROW1_Y + CONF_ROW_H + 20,
|
||
"VOL^V NAV PLAY OK POWER EXIT", 1, 0x666688);
|
||
}
|
||
|
||
/* 弹确认框并等待选择。返回 0=否(留下) 1=是(杀播放器) -1=无法绘制/异常。
|
||
* 调用方负责:先 SIGSTOP 播放器(防其重绘覆盖)+ unblank/亮度点亮面板;
|
||
* 返回后调用方按结果处理面板/播放器。本函数只负责 fb0 叠框 + 输入。 */
|
||
static int confirm_switch(void)
|
||
{
|
||
if (fb_open() != 0) { fprintf(stderr, "confirm: fb open failed -> default NO\n"); return -1; }
|
||
fb_snapshot_player();
|
||
fb_pan_buf0();
|
||
|
||
/* touch drain + 短暂冷却(防 goodix 幽灵触摸;M2.1 缩短到 800ms 不吞首次有效输入) */
|
||
int tfd = open(DEV_TOUCH, O_RDONLY | O_NONBLOCK);
|
||
if (tfd >= 0) {
|
||
unsigned char ev[16];
|
||
while (read(tfd, ev, sizeof ev) == (ssize_t)sizeof ev) { /* discard */ }
|
||
}
|
||
unsigned long long cooldown_until = now_ms() + CONF_COOLDOWN_MS;
|
||
mt_reset(&mt_touch); /* 清触摸手势态(down/valid/origin/last),避免上次残留误派发 */
|
||
|
||
struct keypoll kp_keys, kp_power;
|
||
if (kp_open(&kp_keys, DEV_KEYS) != 0) fprintf(stderr, "confirm: WARN no keys dev\n");
|
||
if (kp_open(&kp_power, DEV_POWER) != 0) fprintf(stderr, "confirm: WARN no power dev\n");
|
||
|
||
int cursor = CONF_DEFAULT_CURSOR;
|
||
int prev_vold = 0, prev_volu = 0, prev_play = 0, prev_pwr = 0;
|
||
|
||
/* 键基线:把「当前已按住的键」写进 prev,避免热键残留的电源按住被误判为
|
||
* 新的下沿而立即取消(电源仍按住,kp.poll 的 prev 已同步,但本地 prev_pwr
|
||
* 未同步——launcher run_menu 同款处理,§18.17)。 */
|
||
if (kp_keys.ok) {
|
||
kp_poll(&kp_keys, NULL, NULL);
|
||
if (kp_is_down(&kp_keys, KEY_VOLUMEDOWN)) prev_vold = 1;
|
||
if (kp_is_down(&kp_keys, KEY_VOLUMEUP)) prev_volu = 1;
|
||
if (kp_is_down(&kp_keys, KEY_PLAYPAUSE)) prev_play = 1;
|
||
}
|
||
if (kp_power.ok) {
|
||
kp_poll(&kp_power, NULL, NULL);
|
||
if (kp_is_down(&kp_power, KEY_POWER)) prev_pwr = 1;
|
||
}
|
||
|
||
draw_confirm_box(cursor);
|
||
fprintf(stderr, "confirm: box shown (default cursor %d)\n", cursor);
|
||
|
||
int result = -1;
|
||
unsigned long long box_t0 = now_ms();
|
||
for (;;) {
|
||
/* --sim-confirm Y|N:框打开 2s 后自动回答 */
|
||
if (sim_confirm && !sim_confirm_done && now_ms() - box_t0 >= 2000) {
|
||
sim_confirm_done = 1;
|
||
result = (sim_confirm == 'Y') ? 1 : 0;
|
||
fprintf(stderr, "confirm: SIM answer %c -> %s\n", sim_confirm,
|
||
result == 1 ? "YES" : "NO");
|
||
break;
|
||
}
|
||
|
||
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) { cursor = (cursor + 1) % 2; draw_confirm_box(cursor); }
|
||
if (volu && !prev_volu) { cursor = (cursor - 1 + 2) % 2; draw_confirm_box(cursor); }
|
||
if (play && !prev_play) { result = (cursor == 0) ? 1 : 0; break; }
|
||
if (pwr && !prev_pwr) { result = 0; break; } /* 电源=取消=否 */
|
||
prev_vold = vold; prev_volu = volu; prev_play = play; 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);
|
||
uint16_t 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_touch, val, mt_touch.last_y);
|
||
else if (code == ABS_Y || code == ABS_MT_POSITION_Y) mt_abs(&mt_touch, mt_touch.last_x, val);
|
||
} else if (type == EV_KEY && code == BTN_TOUCH) {
|
||
if (val) { /* 按压边沿:接受但**不锁原点**——等按下后首个有效 ABS 才锁(goodix 时序免疫) */
|
||
if (now_ms() < cooldown_until) mt_reset(&mt_touch); /* 幽灵/冷却:丢弃本次 */
|
||
else mt_down(&mt_touch);
|
||
} else { /* 抬起边沿:按按下原点派发;滑动/未锁到坐标 → mt_up 返回 0,不命中、不崩溃 */
|
||
int dx = 0, dy = 0;
|
||
if (mt_up(&mt_touch, &dx, &dy)) {
|
||
int ty = mt_touch.down_y;
|
||
if (ty >= CONF_ROW0_Y && ty < CONF_ROW0_Y + CONF_ROW_H) { result = 1; break; }
|
||
else if (ty >= CONF_ROW1_Y && ty < CONF_ROW1_Y + CONF_ROW_H) { result = 0; break; }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (result >= 0) break;
|
||
}
|
||
}
|
||
usleep(POLL_MS * 1000);
|
||
}
|
||
|
||
if (tfd >= 0) close(tfd);
|
||
fb_restore_player_frame();
|
||
fb_close();
|
||
fprintf(stderr, "confirm: result %s\n", result == 1 ? "YES (kill player)" :
|
||
result == 0 ? "NO (stay HibyOS)" : "unknown");
|
||
return result;
|
||
}
|
||
|
||
/* ---- HibyOS 分支:父进程监督者/热键守护(M2)---- */
|
||
static void bootmenu_supervisor(int player_pid)
|
||
{
|
||
/* 记录开机后的正常亮度,供「杀播放器后亮度==0 则恢复 saved/60」。
|
||
* 此刻播放器刚拉起,可能已写入其背光值;过早读到 0 则回退默认 60。 */
|
||
int saved_brightness = read_brightness();
|
||
/* M2.9 Bug 3:开机初期播放器启动画面会把背光设为瞬时高值(本机 max=101),
|
||
* 若直接以其为「保底亮度」,会在扫描窗首次写回时跳一次最高亮度(开机瞬间闪一下)。
|
||
* 只信任稳定读数:读到 0 或异常高(>稳定上限 60,适中非最大)一律回退稳定值 60,
|
||
* 确保扫描期写回平滑(只写目标一次、不跳 max)。 */
|
||
if (saved_brightness <= 0 || saved_brightness > 60) saved_brightness = 60;
|
||
fprintf(stderr, "supervisor: player pid=%d, saved brightness=%d, monitoring...\n",
|
||
player_pid, saved_brightness);
|
||
/* 信号记录 hook(M2 诊断):先装 handler、start 一行写入 TF 卡日志。
|
||
* 只记录,不改行为(handler 仅置标志,主循环 log 后恢复默认重抛)。 */
|
||
bm_sig_install();
|
||
bm_log("supervisor: start pid=%d saved_brightness=%d mode=hiby ver=%s",
|
||
player_pid, saved_brightness, MEOW_BOOTMENU_VER);
|
||
/* Bug 4:HibyOS 开机媒体扫描窗内做亮度保底。播放器忙(媒体扫描)时其背光
|
||
* 待机会把亮度降到 0、面板 blank → 黑屏几分钟。supervisor 在扫描窗内若读到
|
||
* 亮度 0,写回保底亮度(saved 或 60)。**只写 brightness、绝不碰 FBIOBLANK**
|
||
* (不抢播放器的面板归属语义,参考 §18.30 面板归属)。扫描窗结束即放手。 */
|
||
unsigned long long scan_keep_until = now_ms() + SCAN_KEEP_MS;
|
||
unsigned long long last_bri_floor_at = 0; /* 亮度保底节流:≥400ms 才再写一次 */
|
||
|
||
struct keypoll kp_power, kp_keys;
|
||
if (kp_open(&kp_power, DEV_POWER) != 0) fprintf(stderr, "supervisor: WARN no power dev\n");
|
||
if (kp_open(&kp_keys, DEV_KEYS) != 0) fprintf(stderr, "supervisor: WARN no keys dev\n");
|
||
|
||
int prev_pwr = 0, prev_vold = 0;
|
||
int pwr_hold_armed = 0;
|
||
unsigned long long pwr_down_at = 0;
|
||
int player_alive = 1;
|
||
int hotkey_pending = 0; /* M2.14:热键在武装延迟/播放器忙时置 pending,条件满足后自动弹框 */
|
||
unsigned long long sim_t0 = now_ms(); /* --sim-hotkey: supervisor 启动计时 */
|
||
unsigned long long last_mtd5_check = 0; /* T2 升级接管:mtd5 轮询节流 */
|
||
|
||
for (;;) {
|
||
unsigned long long t = now_ms();
|
||
|
||
/* 0) 记录 supervisor 收到的信号(SIGTERM/SIGINT/SIGQUIT/SIGHUP)。
|
||
* 只记录后恢复默认重抛——不改行为(仍按今日方式终止)。 */
|
||
bm_sig_poll();
|
||
|
||
/* 0b) T2 升级接管:每 500ms 读一次 mtd5 头字节,命中 ota:kernel2 即判
|
||
* 「播放器 UI 触发升级」(nandwrite 已写标记但 system 呆在最后一帧没 reboot)
|
||
* → supervisor 执行 reboot → uboot 引导 kernel2(升级模式,自动刷固件)。
|
||
* 只读 ~15B/500ms,IO 极低;/proc/mtd 只反映分区表不变读,观察不了内容。 */
|
||
if (now_ms() - last_mtd5_check >= 500) {
|
||
last_mtd5_check = now_ms();
|
||
if (mtd_ota_is_upgrade()) {
|
||
bm_log("supervisor 接管 upgrade: mtd5=ota:kernel2 -> reboot");
|
||
bm_do_reboot(LINUX_REBOOT_CMD_RESTART);
|
||
}
|
||
}
|
||
|
||
/* 1) waitpid(WNOHANG):播放器退出 → 记日志;自然退出态 0(UI 关机)→ 真关机
|
||
* (M2.29 接管,避免「卡最后一帧但系统活着/SSH 能连」);其余不自动重启(HGL)。 */
|
||
if (player_alive) {
|
||
int st = 0;
|
||
pid_t r = waitpid(player_pid, &st, WNOHANG);
|
||
if (r == player_pid) {
|
||
player_alive = 0;
|
||
if (WIFSIGNALED(st)) {
|
||
bm_log("player exited by signal %d (%s)", WTERMSIG(st), bm_sig_name(WTERMSIG(st)));
|
||
} else if (WIFEXITED(st) && WEXITSTATUS(st) == 0) {
|
||
/* T2 关机接管:播放器 **自然** exit(0)(非热键 SIGTERM 杀)——
|
||
* 热键杀仍在 confirm 分支内 waitpid,不会走到这里。 */
|
||
bm_log("supervisor 接管 shutdown: player exit 0 -> poweroff");
|
||
bm_do_reboot(LINUX_REBOOT_CMD_POWER_OFF);
|
||
} else {
|
||
bm_log("player exited status=%d", WEXITSTATUS(st));
|
||
/* 阶段 0:不自动重启(HGL 不可二次获取,§18.21.4)。 */
|
||
}
|
||
} else if (r < 0) {
|
||
player_alive = 0; /* 已被收割/错误 */
|
||
}
|
||
}
|
||
|
||
/* 2a) Bug 4 亮度保底:扫描窗内、播放器存活且亮度被降到 0 → 写回保底亮度。
|
||
* (播放器扫描忙时无法响应唤醒,我们只把背光拉回,画面仍为播放器最后帧,
|
||
* 不再黑屏数分钟;不碰 blank。) */
|
||
if (player_alive && now_ms() < scan_keep_until) {
|
||
int bri = read_brightness();
|
||
if (bri <= 0 && now_ms() - last_bri_floor_at >= 400) {
|
||
int floor = saved_brightness > 0 ? saved_brightness : 60;
|
||
write_brightness(floor);
|
||
last_bri_floor_at = now_ms();
|
||
fprintf(stderr, "supervisor: scan-window brightness floor -> %d\n", floor);
|
||
}
|
||
}
|
||
|
||
/* 2) EVIOCGKEY 采样(event0 电源 / event2 音量) */
|
||
int pwr = 0, vold = 0;
|
||
if (kp_power.ok) { kp_poll(&kp_power, NULL, NULL); pwr = kp_is_down(&kp_power, KEY_POWER); }
|
||
if (kp_keys.ok) { kp_poll(&kp_keys, NULL, NULL); vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN); }
|
||
|
||
/* --sim-hotkey N:supervisor 启动后 N ms 模拟「电源按住 3s + N+800 音量减点按」,
|
||
* 驱动同一套热键判定(与 launcher --sim-hotkey 同构,§18.17 注入不可投递)。 */
|
||
if (sim_hotkey_ms > 0 && !sim_hk_done) {
|
||
long se = (long)(now_ms() - sim_t0);
|
||
if (se >= sim_hotkey_ms && se < sim_hotkey_ms + 3000) pwr = 1;
|
||
if (se >= sim_hotkey_ms + 800 && se < sim_hotkey_ms + 950) vold = 1;
|
||
}
|
||
|
||
/* 3) 热键:电源按下边沿 arming,窗口内音量减按下边沿触发 */
|
||
if (pwr && !prev_pwr) { pwr_hold_armed = 1; pwr_down_at = t; }
|
||
if (!pwr && prev_pwr) pwr_hold_armed = 0; /* 松开:disarm */
|
||
if (pwr_hold_armed && pwr && (t - pwr_down_at) > HK_PWRWIN_MAX)
|
||
pwr_hold_armed = 0; /* 超窗:disarm */
|
||
|
||
int hotkey_fired = 0;
|
||
if (pwr_hold_armed && pwr && vold && !prev_vold) {
|
||
long since = (long)(t - pwr_down_at);
|
||
if (since >= HK_PWRWIN_MIN && since <= HK_PWRWIN_MAX) {
|
||
fprintf(stderr, "supervisor: HOTKEY power held + VOL_DOWN tap (%ldms) -> switch to MeowOS\n",
|
||
since);
|
||
hotkey_fired = 1;
|
||
} else {
|
||
fprintf(stderr, "supervisor: POWER+VOL_DOWN ignored (%ldms outside window)\n", since);
|
||
}
|
||
}
|
||
prev_pwr = pwr; prev_vold = vold;
|
||
|
||
/* 4) 热键触发 → 弹确认框 → 是:杀播放器进 MeowOS;否:留在 HibyOS。
|
||
* 「是/否」分支严格按用户定案:否不移交给 MeowOS,
|
||
* 恢复播放器息屏副作用并 SIGCONT;是才 kill。
|
||
* M2.14:开机武装延迟 + 播放器忙判门不再「静默丢弃」——热键一旦命中即置
|
||
* hotkey_pending,待条件满足(延迟结束 / 播放器不忙)后**自动**弹确认框,用户只按一次。 */
|
||
int in_armed_delay = (sim_hotkey_ms <= 0 || sim_hotkey_strict) && (now_ms() - sim_t0) < ARM_DELAY_MS;
|
||
int player_busy = (sim_hotkey_ms <= 0 || sim_hotkey_strict) && player_alive && proc_is_busy(player_pid);
|
||
if (hotkey_pending && !in_armed_delay && !player_busy) {
|
||
hotkey_fired = 1; hotkey_pending = 0;
|
||
fprintf(stderr, "supervisor: HOTKEY pending cleared -> fire confirm box\n");
|
||
}
|
||
if (hotkey_fired) {
|
||
sim_hk_done = 1; /* --sim-hotkey 触发一次后不再模拟 */
|
||
/* §18.23:开机武装延迟 / 播放器忙判门——不静默丢弃,置 pending 待条件满足后自动弹框
|
||
* (仅约束真实热键;--sim-hotkey 为显式测试钩子,直通)。 */
|
||
if (in_armed_delay || player_busy) {
|
||
hotkey_pending = 1;
|
||
fprintf(stderr, "supervisor: hotkey queued (armed delay=%d busy=%c) -> pending\n",
|
||
(int)in_armed_delay, player_busy ? proc_state(player_pid) : '-');
|
||
pwr_hold_armed = 0;
|
||
kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL);
|
||
prev_pwr = kp_is_down(&kp_power, KEY_POWER);
|
||
prev_vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN);
|
||
continue;
|
||
}
|
||
/* 刷新 saved 亮度:若播放器当前亮度仍为正(如「背光常亮」设置),
|
||
* 以它为准;若电源按下已触发灭屏(亮度 0),保留此前 saved(≥60)。 */
|
||
int now_bri = read_brightness();
|
||
if (now_bri > 0) saved_brightness = now_bri;
|
||
/* 先冻结播放器(SIGSTOP→T),防止其在确认框上重绘覆盖;
|
||
* 同时捕获面板是否已因电源按下而灭屏(was_blank),供较后恢复。 */
|
||
if (player_alive && !proc_is_stopped(player_pid)) {
|
||
bm_log("supervisor -> player %d SIGSTOP (freeze for confirm)", player_pid);
|
||
kill(player_pid, SIGSTOP);
|
||
for (int i = 0; i < 100 && !proc_is_stopped(player_pid); i++) usleep(10000);
|
||
}
|
||
int raw_brightness = read_brightness();
|
||
int was_blank = (raw_brightness <= 0);
|
||
|
||
/* 点亮面板以便看清确认框(播放器已冻结,不会 re-blank)。 */
|
||
unblank_panel();
|
||
if (was_blank) write_brightness(saved_brightness);
|
||
if (read_file_int(BACKLIGHT_POWER) > 0) write_file_str(BACKLIGHT_POWER, "0\n");
|
||
|
||
int ans = confirm_switch();
|
||
|
||
if (ans == 1) {
|
||
/* ===== 是:杀播放器 → 进 MeowOS ===== */
|
||
if (player_alive) {
|
||
/* 帧冻结是 T 态:先 SIGCONT 让 SIGTERM 可投递(§18.21.3) */
|
||
if (proc_is_stopped(player_pid)) {
|
||
fprintf(stderr, "supervisor: player is T, SIGCONT first\n");
|
||
bm_log("supervisor -> player %d SIGCONT (resume from T)", player_pid);
|
||
kill(player_pid, SIGCONT);
|
||
}
|
||
fprintf(stderr, "supervisor: SIGTERM %d\n", player_pid);
|
||
bm_log("supervisor -> player %d SIGTERM (hotkey -> MeowOS)", player_pid);
|
||
kill(player_pid, SIGTERM);
|
||
unsigned long long kstart = now_ms();
|
||
int exited = 0;
|
||
while (now_ms() - kstart < KILL_GRACE_MS) {
|
||
int st = 0;
|
||
if (waitpid(player_pid, &st, WNOHANG) == player_pid) { exited = 1; break; }
|
||
usleep(POLL_MS * 1000);
|
||
}
|
||
if (!exited) {
|
||
fprintf(stderr, "supervisor: player not exiting in %dms, SIGKILL\n", KILL_GRACE_MS);
|
||
bm_log("supervisor -> player %d SIGKILL (grace %dms expired)", player_pid, KILL_GRACE_MS);
|
||
kill(player_pid, SIGKILL);
|
||
int st = 0;
|
||
waitpid(player_pid, &st, 0);
|
||
}
|
||
player_alive = 0;
|
||
}
|
||
/* 播放器已死:面板已在确认框点亮(brightness=saved)。写 mode=meow。
|
||
* M2.83 迁移:MeowOS 基础主屏(launcher)已交 sysui 托管——sysui 在则让 sysui
|
||
* 拉起 launcher(本 supervisor 让位退出,rcS 推进,S92 见 mode=meow 跳过播放器);
|
||
* sysui 不在则照旧 exec launcher(兜底)。 */
|
||
write_mode(0);
|
||
if (enter_meow_launcher() == 0) {
|
||
fprintf(stderr, "supervisor: launcher handed off to sysui, supervisor exits\n");
|
||
fflush(NULL);
|
||
_exit(0);
|
||
}
|
||
/* 不可达:enter_meow_launcher 在无 sysui 时已 exec launcher(不返回) */
|
||
_exit(127);
|
||
} else {
|
||
/* ===== 否 / -1:留在 HibyOS =====
|
||
* 恢复播放器息屏副作用:进框前已灭屏则 re-blank(0+FBIOBLANK 4)
|
||
* 使物理面板匹配播放器 state machine;否则仅当读回 0 才写回 saved。 */
|
||
if (was_blank) {
|
||
write_brightness(0);
|
||
int f = open(FB_DEV, O_RDWR);
|
||
if (f >= 0) { ioctl(f, FBIOBLANK, 4); close(f); }
|
||
} else if (read_brightness() <= 0) {
|
||
write_brightness(saved_brightness);
|
||
}
|
||
fprintf(stderr, "supervisor: hotkey cancelled (stay HibyOS)\n");
|
||
if (player_alive && proc_is_stopped(player_pid)) {
|
||
bm_log("supervisor -> player %d SIGCONT (hotkey cancelled)", player_pid);
|
||
kill(player_pid, SIGCONT);
|
||
}
|
||
/* 消化键边沿基线:把 prev 对齐到当前实际按下态(含电源仍按住),
|
||
* 并 disarm 热键,避免残留边沿立即重触发新热键(误弹确认框)。 */
|
||
pwr_hold_armed = 0;
|
||
kp_poll(&kp_power, NULL, NULL);
|
||
kp_poll(&kp_keys, NULL, NULL);
|
||
prev_pwr = kp_is_down(&kp_power, KEY_POWER);
|
||
prev_vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN);
|
||
continue;
|
||
}
|
||
}
|
||
|
||
usleep(POLL_MS * 1000);
|
||
}
|
||
}
|
||
|
||
/* ================================================================== */
|
||
/* 模式标记 + 决策 */
|
||
/* ================================================================== */
|
||
static void write_mode(int choice)
|
||
{
|
||
/* choice: 1=HibyOS 0=MeowOS */
|
||
FILE *f = fopen(MODE_FILE, "w");
|
||
if (f) { fprintf(f, "%s\n", choice == 1 ? "hiby" : "meow"); fclose(f); }
|
||
fprintf(stdout, "bootmenu: DECISION -> %s (mode=%s)\n",
|
||
choice == 1 ? "HIBYOS 官方播放器" : "MEOWOS MeowOS 主屏",
|
||
choice == 1 ? "hiby" : "meow");
|
||
}
|
||
|
||
/* Bug 1:选 MeowOS → 把它持久为开机默认(写 spec 标记 + meow.conf boot_default=0),与 launcher
|
||
* «回到HibyOS»(set_boot_default_hiby 写 boot_default=1)对称。效果:本次选中即进 MeowOS 主屏,且
|
||
* 下一次开机选单默认 MeowOS(不再「选 MeowOS 仍落回 HibyOS」)。
|
||
* 权威标记 = 板载 /data/meow_boot_default(O_TRUNC 单值);meow.conf 不再写/承载 boot_default。 */
|
||
static void set_boot_default_meow(void)
|
||
{
|
||
int fd = open("/data/meow_boot_default", O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||
if (fd >= 0) { write(fd, "0\n", 2); close(fd); fprintf(stderr, "bootmenu: /data/meow_boot_default=0\n"); }
|
||
}
|
||
|
||
/* ================================================================== */
|
||
/* --dumpkeys N:轮询 N ms,打印按键边沿(真机抓 key code 用) */
|
||
/* ================================================================== */
|
||
static int dump_cb(int code, int down, void *ctx)
|
||
{
|
||
unsigned long long start = *(unsigned long long *)ctx;
|
||
fprintf(stdout, "[%llums] key %d %s\n", now_ms() - start, code,
|
||
down ? "DOWN" : "UP");
|
||
return 0;
|
||
}
|
||
|
||
static int run_dumpkeys(long ms)
|
||
{
|
||
struct keypoll kp_power, kp_keys;
|
||
int p_ok = kp_open(&kp_power, DEV_POWER) == 0;
|
||
int k_ok = kp_open(&kp_keys, DEV_KEYS) == 0;
|
||
touch_fd = open(DEV_TOUCH, O_RDONLY | O_NONBLOCK);
|
||
if (touch_fd < 0) fprintf(stderr, "dumpkeys: open %s: %s\n", DEV_TOUCH, strerror(errno));
|
||
fprintf(stderr, "dumpkeys: polling %ld ms (power=%d keys=%d touch=%d)\n",
|
||
ms, p_ok, k_ok, touch_fd >= 0);
|
||
unsigned long long start = now_ms();
|
||
while ((long)(now_ms() - start) < ms) {
|
||
if (p_ok) kp_poll(&kp_power, dump_cb, &start);
|
||
if (k_ok) kp_poll(&kp_keys, dump_cb, &start);
|
||
if (touch_fd >= 0) {
|
||
struct pollfd pfd = { touch_fd, POLLIN, 0 };
|
||
if (poll(&pfd, 1, 0) > 0) {
|
||
unsigned char ev[16];
|
||
while (read(touch_fd, ev, sizeof ev) == (ssize_t)sizeof ev) {
|
||
uint16_t type = *(uint16_t *)(ev + 8);
|
||
uint16_t 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_touch, val, mt_touch.last_y);
|
||
else if (code == ABS_Y || code == ABS_MT_POSITION_Y) mt_abs(&mt_touch, mt_touch.last_x, val);
|
||
} else if (type == EV_KEY && code == BTN_TOUCH) {
|
||
fprintf(stdout, "[%llums] touch BTN_TOUCH %d @ (%d,%d)\n",
|
||
now_ms() - start, val, mt_touch.last_x, mt_touch.last_y);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
usleep(POLL_MS * 1000);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* ================================================================== */
|
||
/* 选单守护模式主循环 */
|
||
/* ================================================================== */
|
||
static int run_menu(int sim_sel, int do_fork)
|
||
{
|
||
struct keypoll kp_power, kp_keys;
|
||
if (kp_open(&kp_power, DEV_POWER) != 0) fprintf(stderr, "bootmenu: WARN no power dev\n");
|
||
if (kp_open(&kp_keys, DEV_KEYS) != 0) fprintf(stderr, "bootmenu: WARN no keys dev\n");
|
||
touch_fd = open(DEV_TOUCH, O_RDONLY | O_NONBLOCK);
|
||
if (touch_fd < 0) fprintf(stderr, "bootmenu: WARN no touch dev\n");
|
||
|
||
/* 入口 drain + 短暂冷却(防 goodix 幽灵触摸,§18.16;M2.1 缩短到 800ms 不吞首次有效输入) */
|
||
if (touch_fd >= 0) {
|
||
unsigned char ev[16];
|
||
while (read(touch_fd, ev, sizeof ev) == (ssize_t)sizeof ev) { /* discard */ }
|
||
}
|
||
unsigned long long cooldown_until = now_ms() + GHOST_COOLDOWN_MS;
|
||
mt_reset(&mt_touch); /* 清触摸手势态,避免上次残留误派发(goodix 时序免疫:last 归 -1 起步) */
|
||
|
||
int cursor = (cfg_boot_default == 1) ? 0 : 1; /* 高亮随默认 */
|
||
unsigned long long start = now_ms();
|
||
unsigned long long deadline = start + (unsigned long long)cfg_boot_timeout_ms; /* 用户操作→重计(倒计时交互) */
|
||
int dirty = 1, last_cursor = -1;
|
||
long last_clock_tick = -10000, last_cd_tick = -10000;
|
||
int prev_vold = 0, prev_volu = 0, prev_play = 0, prev_pwr = 0;
|
||
int sim_done = 0;
|
||
int choice = -1;
|
||
|
||
for (;;) {
|
||
unsigned long long t = now_ms();
|
||
long el = (long)(t - start);
|
||
long remaining = (long)(deadline - t); /* 距 deadline:空闲才倒数,有输入则重计 */
|
||
|
||
if (remaining <= 0) { /* 空闲超时 → 单调强收敛到默认 */
|
||
choice = cfg_boot_default;
|
||
fprintf(stderr, "bootmenu: TIMEOUT -> default %s\n",
|
||
choice == 1 ? "hiby" : "meow");
|
||
break;
|
||
}
|
||
|
||
/* --sim-select H|M:在 SIM_SELECT_DELAY_MS 后模拟确认 */
|
||
if (sim_sel && !sim_done && el >= SIM_SELECT_DELAY_MS) {
|
||
sim_done = 1;
|
||
if (sim_sel == 'H') { cursor = 0; choice = 1; }
|
||
else { cursor = 1; choice = 0; }
|
||
fprintf(stderr, "bootmenu: SIM SELECT %c -> %s\n",
|
||
sim_sel, choice == 1 ? "hiby" : "meow");
|
||
break;
|
||
}
|
||
|
||
/* 按键边沿 */
|
||
int vold = 0, volu = 0, play = 0, pwr = 0;
|
||
if (kp_power.ok) { kp_poll(&kp_power, NULL, NULL); pwr = kp_is_down(&kp_power, KEY_POWER); }
|
||
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 (vold && !prev_vold) { /* 音量下 = 下移 */
|
||
cursor = (cursor + 1) % 2;
|
||
deadline = now_ms() + (unsigned long long)cfg_boot_timeout_ms; /* 用户操作 → 倒计时重计 */
|
||
fprintf(stderr, "bootmenu: key VOL_DOWN -> cursor %d (cd reset)\n", cursor);
|
||
}
|
||
if (volu && !prev_volu) { /* 音量上 = 上移(2 项循环:-1 取模) */
|
||
cursor = (cursor - 1 + 2) % 2;
|
||
deadline = now_ms() + (unsigned long long)cfg_boot_timeout_ms; /* 用户操作 → 倒计时重计 */
|
||
fprintf(stderr, "bootmenu: key VOL_UP -> cursor %d (cd reset)\n", cursor);
|
||
}
|
||
if (play && !prev_play) { /* 播放 = 确认 */
|
||
choice = (cursor == 0) ? 1 : 0;
|
||
fprintf(stderr, "bootmenu: key PLAY -> select row %d (%s)\n",
|
||
cursor, choice == 1 ? "hiby" : "meow");
|
||
break;
|
||
}
|
||
if (pwr && !prev_pwr) { /* 电源 = 确认当前光标项(M2.9 Bug 1:不再固定取默认) */
|
||
choice = (cursor == 0) ? 1 : 0;
|
||
fprintf(stderr, "bootmenu: key POWER -> confirm row %d (%s)\n",
|
||
cursor, choice == 1 ? "hiby" : "meow");
|
||
break;
|
||
}
|
||
prev_vold = vold; prev_volu = volu; prev_play = play; prev_pwr = pwr;
|
||
|
||
/* 触摸(event1):按压→抬起 tap(同 launcher,读 ABS_X/Y 与 ABS_MT_*,§18.20) */
|
||
if (touch_fd >= 0) {
|
||
struct pollfd pfd = { touch_fd, POLLIN, 0 };
|
||
if (poll(&pfd, 1, 0) > 0) {
|
||
unsigned char ev[16];
|
||
while (read(touch_fd, ev, sizeof ev) == (ssize_t)sizeof ev) {
|
||
uint16_t type = *(uint16_t *)(ev + 8);
|
||
uint16_t 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_touch, val, mt_touch.last_y);
|
||
else if (code == ABS_Y || code == ABS_MT_POSITION_Y) mt_abs(&mt_touch, mt_touch.last_x, val);
|
||
} else if (type == EV_KEY && code == BTN_TOUCH) {
|
||
if (val) { /* 按压边沿:接受但**不锁原点**——等按下后首个有效 ABS 才锁(goodix 时序免疫,修“点两下”) */
|
||
if (now_ms() < cooldown_until) mt_reset(&mt_touch); /* 幽灵/冷却:丢弃本次 */
|
||
else mt_down(&mt_touch);
|
||
} else { /* 抬起边沿:按按下原点派发;滑动/未锁到坐标 → mt_up 返回 0,不命中、不崩溃 */
|
||
int dx = 0, dy = 0;
|
||
if (mt_up(&mt_touch, &dx, &dy)) {
|
||
int ty = mt_touch.down_y;
|
||
if (ty >= ENTRY0_Y - 4 && ty < ENTRY0_Y + ROW_H - 8) {
|
||
cursor = 0; choice = 1; deadline = now_ms() + (unsigned long long)cfg_boot_timeout_ms;
|
||
fprintf(stderr, "bootmenu: touch row 0 -> hiby\n");
|
||
} else if (ty >= ENTRY1_Y - 4 && ty < ENTRY1_Y + ROW_H - 8) {
|
||
cursor = 1; choice = 0; deadline = now_ms() + (unsigned long long)cfg_boot_timeout_ms;
|
||
fprintf(stderr, "bootmenu: touch row 1 -> meow\n");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (choice >= 0) break;
|
||
}
|
||
}
|
||
|
||
/* 光标变化 → 整屏重绘 */
|
||
if (cursor != last_cursor) dirty = 1;
|
||
if (dirty) {
|
||
draw_menu(cursor, remaining);
|
||
last_cursor = cursor;
|
||
dirty = 0;
|
||
}
|
||
|
||
/* 每秒刷时钟 */
|
||
if (el - last_clock_tick >= 1000) { last_clock_tick = el; draw_clock(cfg_bg); }
|
||
/* 每秒刷倒计时(秒值变化才刷) */
|
||
long cd = (remaining + 999) / 1000;
|
||
if (el - last_cd_tick >= 1000) { last_cd_tick = el; draw_countdown(cfg_bg, remaining); }
|
||
|
||
usleep(POLL_MS * 1000);
|
||
}
|
||
|
||
write_mode(choice >= 0 ? choice : cfg_boot_default);
|
||
if (touch_fd >= 0) close(touch_fd);
|
||
|
||
/* 顺序(§2.3):先恢复 fb(选单不再占屏),再 munmap+close fb0,才 fork */
|
||
fb_restore_player_frame();
|
||
fb_close();
|
||
/* M2(--fork):HibyOS→fork 播放器(父进程转监督者,阻塞 rcS);
|
||
* MeowOS→sysui 在则让 sysui 拉起 launcher 并让位(rcS 推进,S92 见 mode=meow 跳过播放器);
|
||
* sysui 不在则 exec meow_launcherd(MeowOS 基础主屏,接管 fb0;兜底)。返回=已让位。 */
|
||
if (do_fork) {
|
||
if (choice == 1) {
|
||
fork_hiby_player(); /* 父进程陷入 supervisor,永不返回 */
|
||
} else {
|
||
set_boot_default_meow(); /* Bug 1:选 MeowOS → 持久为开机默认 */
|
||
enter_meow_launcher(); /* 让位返回 0,或 exec launcher(不返回) */
|
||
}
|
||
}
|
||
return 0; /* M0/M1(无 --fork):选单退出,rcS 推进 */
|
||
}
|
||
|
||
/* ================================================================== */
|
||
/* main */
|
||
/* ================================================================== */
|
||
static void usage(void)
|
||
{
|
||
fprintf(stderr,
|
||
"usage: meow_bootmenu [--dumpkeys N] [--sim-select H|M] [--boot-only]\n"
|
||
" [--timeout-ms N] [--fork] [--version]\n"
|
||
" (no args) 守护选单模式(显示菜单,等待选择/超时)\n"
|
||
" --dumpkeys N 轮询 N ms 打印按键边沿(抓 key code)\n"
|
||
" --sim-select H|M 显示菜单,%d ms 后模拟选 H=HibyOS / M=MeowOS\n"
|
||
" --boot-only 跳过选单,直接取默认(boot_default)\n"
|
||
" --timeout-ms N 覆写超时毫秒(测试钩子)\n"
|
||
" --fork M2 开启:选 HibyOS 时 fork 播放器并转监督者;\n"
|
||
" 选 MeowOS 时 sysui 在→让 sysui 拉起 launcher(本进程让位退出),\n"
|
||
" 否则 exec /usr/bin/meow_launcherd(默认关)\n"
|
||
" --sim-hotkey N 监督者启动 N ms 后模拟热键(M2 真机 verif)\n"
|
||
" --sim-confirm Y|N 确认框打开 2s 后自动答 Y=是 / N=否\n"
|
||
" --version 打印版本并退出\n",
|
||
SIM_SELECT_DELAY_MS);
|
||
}
|
||
|
||
int main(int argc, char **argv)
|
||
{
|
||
long dumpkeys = 0, timeout_override = 0;
|
||
int sim_sel = 0;
|
||
int boot_only = 0;
|
||
int do_fork = 0;
|
||
int do_version = 0;
|
||
|
||
for (int i = 1; i < argc; i++) {
|
||
if (!strcmp(argv[i], "--dumpkeys") && i + 1 < argc) dumpkeys = atol(argv[++i]);
|
||
else if (!strcmp(argv[i], "--sim-select") && i + 1 < argc) {
|
||
char c = argv[++i][0];
|
||
if (c == 'h' || c == 'H') sim_sel = 'H';
|
||
else if (c == 'm' || c == 'M') sim_sel = 'M';
|
||
else { usage(); return 1; }
|
||
}
|
||
else if (!strcmp(argv[i], "--boot-only")) boot_only = 1;
|
||
else if (!strcmp(argv[i], "--timeout-ms") && i + 1 < argc) timeout_override = atol(argv[++i]);
|
||
else if (!strcmp(argv[i], "--fork")) do_fork = 1;
|
||
else if (!strcmp(argv[i], "--sim-hotkey") && i + 1 < argc) sim_hotkey_ms = atol(argv[++i]);
|
||
else if (!strcmp(argv[i], "--sim-hotkey-strict") && i + 1 < argc) { sim_hotkey_ms = atol(argv[++i]); sim_hotkey_strict = 1; }
|
||
else if (!strcmp(argv[i], "--sim-confirm") && i + 1 < argc) {
|
||
char c = argv[++i][0];
|
||
if (c == 'y' || c == 'Y') sim_confirm = 'Y';
|
||
else if (c == 'n' || c == 'N') sim_confirm = 'N';
|
||
else { usage(); return 1; }
|
||
}
|
||
else if (!strcmp(argv[i], "--version")) do_version = 1;
|
||
else { usage(); return 1; }
|
||
}
|
||
|
||
load_boot_ver(); /* 读 /etc/meowos_version(版本单点);--version 与选单右下角共用 */
|
||
|
||
if (do_version) {
|
||
printf("meow_bootmenu %s (mipsel static musl)\n", MEOW_BOOTMENU_VER);
|
||
printf("MeowOS v%s\n", g_boot_ver);
|
||
return 0;
|
||
}
|
||
|
||
load_config();
|
||
if (timeout_override > 0) cfg_boot_timeout_ms = timeout_override;
|
||
|
||
if (dumpkeys > 0) return run_dumpkeys(dumpkeys);
|
||
|
||
/* 无菜单直进(boot_menu=0 或 --boot-only):§3「直进默认锁 HGL」 */
|
||
if (boot_only || !cfg_boot_menu) {
|
||
int choice = cfg_boot_default;
|
||
fprintf(stderr, "bootmenu: boot-only -> default %s\n",
|
||
choice == 1 ? "hiby" : "meow");
|
||
write_mode(choice);
|
||
if (do_fork) {
|
||
if (choice == 1) fork_hiby_player();
|
||
else {
|
||
set_boot_default_meow(); /* Bug 1:选 MeowOS → 持久为开机默认 */
|
||
enter_meow_launcher(); /* sysui 在→让位返回 0;否则 exec launcher(不返回) */
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
if (fb_open() != 0) return 1;
|
||
fb_snapshot_player(); /* 快照当前显示缓冲(恢复播放器帧用) */
|
||
fb_pan_buf0(); /* 画前 pan 到 yoffset=0(buffer0) */
|
||
draw_menu(cfg_boot_default == 1 ? 0 : 1, cfg_boot_timeout_ms); /* 预画一帧 */
|
||
run_menu(sim_sel, do_fork);
|
||
return 0;
|
||
}
|