fix(bootmenu): M23 亮度单一权威源(只交源码,二进制由 M43 统一重编)

真机 0.8.46 两个症状:
1) 开机菜单亮度 = 第一屏的 60(用户设置 1)——根因是 M6.25 的选单亮度写回只改了源码、
   二进制没重编(strings 无 display.brightness_min),本次只交源码,重编交给 M43 批次。
2) HibyOS 亮/灭屏瞬间爆亮——supervisor 把启动瞬间面板读数(播放器写的 60)当保底,
   且 >60 一律钉 60;扫描窗保底只要读到亮度<=0 就在 400ms 内写回该值,用户主动熄屏
   也会被强行点亮。

改动(全部失败也沿用现状行为):
- saved_brightness 权威源改为 settings.conf 的 display.brightness(与 watchdog/launcher
  同源同 clamp);面板读数只在 settings 不可读时兜底;两者都不可知才写 BM_BRI_SAFE_LOW。
- 旧的 `>60 → 60` 硬钉改为 clamp 进 [display.brightness_min, max_brightness](bm_bri_clamp),
  不再用常量顶替用户值;为什么不会重新引入 M2.9 开机闪屏写在 bm_bri_pick_saved 注释里。
- 扫描窗保底加「播放器忙(proc 态 D)+ 面板未下电(bl_power<=0)」两门,窗口结束彻底放手。
- 热键熄屏恢复:面板真实正值优先(HibyOS 内改过亮度),否则用 settings 值,夹进 [min,max]。
- 决策抽纯函数 bm_bri_pick_saved / bm_bri_clamp / bm_bri_floor_decide / bm_bri_hotkey_saved。
- 亮度判定/写回补 bm_log 落卡(stderr 原行全部保留),只在状态跃迁时打,不在高频路径刷日志。
- test_bm_fw.c:宿主单测(settings=1/saved=60/now=0/blank 矩阵 + clamp 矩阵 + 真文件 e2e)。
- -Wall -Wextra 清理:3 处 unsigned/int 比较、未用参数、未用变量、sys/poll.h→poll.h。

不含二进制、不改 fw_content.conf 钉值(保持 4441822d)。
This commit is contained in:
AskaEth committed 2026-11-11 18:42:41 +08:00
1 parent 63644985a6
commit bc7bafa5dc
2 files changed
+462 -40

No files matched your search

+207 -40
View File
@@ -32,6 +32,22 @@
* 说明:/proc/mtd 只反映分区表不变读,无法观察内容变化,故用轻量读 /dev/mtd5 轮询。
* 两根都写 bm_log(supervisor 接管 shutdown/upgrade)。
*
* M23 亮度单一权威源(2026-11-11,MEOW_BOOTMENU_VER 0.1.0-m2.30-bri-src):
* 真机症状(0.8.46)「开机菜单亮度=第一屏的 60」「HibyOS 亮/灭屏瞬间爆亮」。根因两处:
* - 开机菜单:M6.25 的「显示前按 settings.conf 写用户亮度」(本文件 main 末尾)只改了源码,
* **二进制没重编**(fw_content.conf 钉的是 8/31 的旧二进制,strings 无 display.brightness_min)
* → 0.8.46/0.8.49 上选单仍继承面板值(0.8.46 = S11 写的 60)。本版重编即带上。
* - HibyOS 亮/灭屏:supervisor 起始把「启动瞬间面板读数」当保底亮度,且 >60 一律钉 60;
* 扫描窗保底只要读到亮度<=0 就 400ms 内写回该值 —— 用户主动熄屏(播放器写 0)会被
* 强行点亮成 60。现改为:settings.conf 的 display.brightness 为唯一权威(与 watchdog
* wd_bri_decide / launcher 同源同 clamp),面板读数只在 settings 不可读时兜底;旧的
* `>60 → 60` 改为 clamp 进 [display.brightness_min, max_brightness](为什么不会重新引入
* M2.9 的开机闪屏见 bm_bri_pick_saved 注释);两者都不可知才写 BM_BRI_SAFE_LOW。
* 扫描窗保底另加「播放器忙(媒体扫描)+ 面板未下电」两门,窗口结束彻底放手。
* 决策全部抽成纯函数(bm_bri_pick_saved / bm_bri_floor_decide / bm_bri_hotkey_saved),
* 宿主单测见 test_bm_fw.c。亮度判定改为 bm_log 落卡(stderr 原行保留)。
* 本单一律**只交源码**:二进制由 M43 rebase 后统一重编并回写 fw_content.conf 钉值。
*
* ============================================================================
* WHY (设计见 BOOT_SELECTOR_DESIGN.md §3)
* ============================================================================
@@ -81,7 +97,7 @@
#include <time.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/poll.h>
#include <poll.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/syscall.h> /* M2.29:supervisor 接管关机/升级,直连 reboot(2) syscall */
@@ -111,6 +127,7 @@
#define FBIOBLANK 0x4611
#endif
#define BACKLIGHT "/sys/class/backlight/backlight_pwm0/brightness"
#define BACKLIGHT_MAX "/sys/class/backlight/backlight_pwm0/max_brightness"
#define BACKLIGHT_POWER "/sys/class/backlight/backlight_pwm0/bl_power"
/* ---- 热键切换(HibyOS→MeowOS):按住电源 + 窗口内单击音量减 ---- */
@@ -161,7 +178,7 @@
#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 MEOW_BOOTMENU_VER "0.1.0-m2.30-bri-src" /* M23:亮度单一权威源(settings.conf),二进制随源码重编 */
/* ---- 版式 ---- */
#define STATUS_H 40
@@ -329,7 +346,7 @@ static int fb_open(void)
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; /* 单缓冲兜底 */
if (fb_yres_virtual < (unsigned)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) {
@@ -345,7 +362,7 @@ static int fb_open(void)
/* 选单进入前快照「当前显示缓冲」一帧(播放器事件驱动不主动重绘,须恢复) */
static void fb_snapshot_player(void)
{
if (fb_yres_virtual <= fb_yres) { fb_orig_yoffset = -1; return; }
if (fb_yres_virtual <= (unsigned)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;
@@ -357,7 +374,7 @@ static void fb_snapshot_player(void)
/* 画前 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_fd < 0 || fb_yres_virtual <= (unsigned)fb_yres) { fb_active_yoffset = 0; return; }
if (fb_active_yoffset == 0) return; /* 已显示 buffer0 */
unsigned char v[160];
memset(v, 0, sizeof v);
@@ -436,6 +453,7 @@ static int amixer_vol(int numid)
/* 28px 状态栏:行1=时钟(左) 电量% WIFI BT(右),行2=VOL nn%(左),y=28 分隔线(40→28,用户定) */
static void draw_statusbar(uint32_t bg)
{
(void)bg; /* 传参保留(调用点已传 cfg_bg),本版状态栏用固定配色 */
char timebuf[16], right[48];
time_t now = time(NULL);
struct tm tm;
@@ -814,9 +832,23 @@ static void draw_menu(int cursor, long remaining_ms)
/* ================================================================== */
/* 面板/背光 helper(复用 meow_launcherd 语义,§18.21.5 / §红线) */
/* ================================================================== */
/* 路径可被 env 覆盖(宿主测试用,与 meow_watchdog 的 MEOW_BACKLIGHT /
* MEOW_SETTINGS_FILE 同款钩子);真机 init 环境无这些变量 → 走宏默认路径。 */
static const char *bm_backlight_path(void)
{
const char *e = getenv("MEOW_BACKLIGHT");
return (e && e[0]) ? e : BACKLIGHT;
}
static const char *bm_settings_path(void)
{
const char *e = getenv("MEOW_SETTINGS_FILE");
return (e && e[0]) ? e : MEOW_SETTINGS_FILE;
}
static int read_brightness(void)
{
int fd = open(BACKLIGHT, O_RDONLY);
int fd = open(bm_backlight_path(), O_RDONLY);
if (fd < 0) return -1;
char buf[16];
int n = read(fd, buf, sizeof buf - 1);
@@ -839,22 +871,39 @@ static void write_brightness(int val)
{
char buf[8];
int l = snprintf(buf, sizeof buf, "%d\n", val);
int fd = open(BACKLIGHT, O_WRONLY);
int fd = open(bm_backlight_path(), 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);
}
/* 面板背光上限(本机 101)。读不到返回 0 = 未知,调用方按「无上限信息」降级处理。 */
static int read_max_brightness(void)
{
int v = read_file_int(BACKLIGHT_MAX);
return v > 0 ? v : 0;
}
/* M6.25:开机选单亮度以**用户设置**为准(/usr/data/meow/settings.conf 的 display.brightness)。
* 根因:选单显示路径从不写背光,开机菜单/第二屏亮度纯继承引导早期面板高值;M2.83 起 launcher
* 「开机选单期不抢屏」怠速,移除了 launcher 提前写用户亮度那一步 → 菜单亮在引导高值上。
* 与 meow_watchdog wd_settings_brightness() 同源解析(同文件、同 clamp:下限 display.brightness_min
* 默认 1=极暗但可见、上限 100=防闪屏),自包含零依赖,不链 launcher meow_settings。返回 clamped
* 值;settings 文件不可读 → 返回 -1,调用方**不动**(退化为现状,保安全——选单阶段只写 brightness、
* 绝不碰 FBIOBLANK/blank,§18.24 纪律)。 */
static int bootmenu_settings_brightness(void)
* 绝不碰 FBIOBLANK/blank,§18.24 纪律)。
* M23:fopen 失败做 BM_SETTINGS_TRIES 次短重试,覆盖 /usr/data 刚挂载 / 原子替换的瞬时窗口
* (与 M20 的 wd_settings_brightness 同策略);只重试 open(rename 原子,已打开的 fd 内容完整)。 */
#define BM_SETTINGS_TRIES 3
#define BM_SETTINGS_GAP_MS 10
static int bootmenu_settings_brightness(int *min_out)
{
FILE *f = fopen(MEOW_SETTINGS_FILE, "r");
FILE *f = NULL;
for (int i = 0; i < BM_SETTINGS_TRIES; i++) {
f = fopen(bm_settings_path(), "r");
if (f) break;
if (i + 1 < BM_SETTINGS_TRIES) usleep(BM_SETTINGS_GAP_MS * 1000);
}
if (!f) return -1;
int b = 101; /* display.brightness 默认(与 launcher 设置表同值,键缺失时 clamp→100) */
int mn = 1; /* display.brightness_min 默认(极暗但可见;键缺失时保底) */
@@ -873,9 +922,77 @@ static int bootmenu_settings_brightness(void)
fclose(f);
if (b < mn) b = mn;
if (b > 100) b = 100;
if (min_out) *min_out = mn;
return b;
}
/* ---- M23:亮度决策(纯函数,宿主单测见 test_bm_fw.c)----
* 与 meow_watchdog wd_bri_decide() 同构:settings 是唯一权威,任何分支都不再写「固定 60」。 */
#define BM_BRI_MIN_DEFAULT 1 /* display.brightness_min 缺省(与 launcher/watchdog 同值:极暗但可见) */
#define BM_BRI_SAFE_LOW 20 /* settings/面板都不可知且面板已灭 → 低亮点亮(≈max 的 1/5,绝不 60) */
/* 把任意亮度读数夹进面板合法区间 [mn, max_brightness](M23:取代旧 `>60 → 60` 的硬钉)。
* mn < 1 时按 1(防黑屏:亮着总比黑屏好,但也不伪造「比用户下限更亮」);
* maxb <= 0(sysfs 读不到)时只夹下限,不臆造上限。
* 为什么这样不会重新引入 M2.9 要压的开机闪屏:见 bm_bri_pick_saved 注释。 */
static int bm_bri_clamp(int v, int mn, int maxb)
{
if (mn < BM_BRI_MIN_DEFAULT) mn = BM_BRI_MIN_DEFAULT;
if (v < mn) v = mn;
if (maxb > 0 && v > maxb) v = maxb;
return v;
}
/* supervisor 起始「保底亮度」取值。
* settings(已 clamp 到 [brightness_min,100])→ 面板读数(>0)→ BM_BRI_SAFE_LOW;
* 后两者再夹进 [brightness_min, max_brightness]。
* *src 回填来源,原样进日志(settings / panel / safe-low)。
* 真机症状来源:旧实现把「启动瞬间面板读数」当权威、且 >60 一律钉 60,用户值 1 被 60 顶掉。
*
* 为什么 clamp[min,max] 不会重新引入 M2.9 Bug 3 的「开机闪一下最高亮度」:
* M2.9 的前置条件是「supervisor 启动时面板上是**播放器启动画面**的瞬时值,还没有人写过用户值」,
* 于是一旦该瞬时值(101)被当作保底、在后续写回时就会跳一次 max。现在三个条件都不成立:
* ① 有 settings 时**根本不看面板值**,写回的就是用户设置本身(写回 == 用户值,不存在跳变);
* ② M21/S22 之后 /usr/data 挂载即写用户值,supervisor 启动时面板上已经不是播放器启动画面的
* 瞬时值,而是用户值本身 —— 降级路径里「观测值」≈「用户值」,clamp 不会把它抬高;
* ③ 写回只发生在「面板已暗」时(扫描窗保底要求 busy 且 未下电;确认框只在 was_blank 时点亮),
* 即「从暗到用户值」,方向永远是向上回到用户原本的亮度,不是从暗跳到 max。
* 换句话说:旧写法的问题不是「60 这个数」,而是「用常量顶替用户值」;clamp 保留的是观测/设置到的
* 合法值,且上限就是面板自己的 max_brightness —— 不会再出现「用户 1、亮屏 60」这种越权抬亮。 */
static int bm_bri_pick_saved(int user, int cur, int mn, int maxb, const char **src)
{
if (user > 0) { *src = "settings"; return bm_bri_clamp(user, mn, maxb); }
if (cur > 0) { *src = "panel"; return bm_bri_clamp(cur, mn, maxb); }
*src = "safe-low";
return bm_bri_clamp(BM_BRI_SAFE_LOW, mn, maxb);
}
/* 扫描窗保底(Bug 4)决策:返回 >0 = 要写的亮度,0 = 不动;*why 回填原因供日志。
* 旧实现只看「亮度<=0」,于是用户主动熄屏(播放器写 0 + blank)会在 400ms 内被强行写回
* saved/60 → 「熄屏瞬间爆亮」。现在要求:窗口内 + 面板暗 + 播放器忙(媒体扫描,proc 态 D)
* + 面板未下电(bl_power<=0)。窗口结束 / 播放器不忙 → 完全放手,不再与播放器抢面板。 */
static int bm_bri_floor_decide(int saved, int cur, int busy, int bl_power,
long long win_left_ms, const char **why)
{
if (win_left_ms <= 0) { *why = "window-over"; return 0; }
if (cur > 0) { *why = "visible"; return 0; }
if (!busy) { *why = "idle-blank"; return 0; }
if (bl_power > 0) { *why = "panel-off"; return 0; }
*why = "scan-floor";
return saved > 0 ? saved : BM_BRI_SAFE_LOW;
}
/* 热键冻结播放器前刷新「熄屏恢复值」:面板当前有正亮度 = 用户此刻看到的真实值,以它为准
* (HibyOS 里改过亮度不写 settings.conf,这条路径必须跟随面板);否则沿用 saved(现在=用户设置);
* 都没有才 BM_BRI_SAFE_LOW。一律夹进 [brightness_min, max_brightness],防越界值回放。 */
static int bm_bri_hotkey_saved(int saved, int now_bri, int mn, int maxb, const char **src)
{
if (now_bri > 0) { *src = "panel-now"; return bm_bri_clamp(now_bri, mn, maxb); }
if (saved > 0) { *src = "saved"; return saved; }
*src = "safe-low";
return bm_bri_clamp(BM_BRI_SAFE_LOW, mn, maxb);
}
/* 面板 unblank(FBIOBLANK 0)。仅用于杀播放器后把屏点亮,以便 MeowOS 接管;
* 播放器已死,故不涉及「播放器 state machine」一致性(§18.11 关注点)。
* bootmenu 选单/自检阶段绝不调用本函数(§18.24 纪律:不碰 panel blank)。 */
@@ -1192,14 +1309,19 @@ static int confirm_switch(void)
/* ---- 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;
/* M23:保底亮度(saved_brightness)的权威源 = settings.conf 的 display.brightness
* (与 launcher / meow_watchdog 同源同 clamp),不再是「启动瞬间面板读数 + >60 钉 60」。
* 旧写法把播放器启动瞬间写入的面板值(真机 60)当权威,用户设置 1 被顶掉,随后扫描窗
* 保底 / 熄屏恢复都把 60 写回 → 「亮/灭屏瞬间爆亮」。
* 取值优先级见 bm_bri_pick_saved:settings → 面板读数 → BM_BRI_SAFE_LOW,后两者夹进
* [brightness_min, max_brightness](取代旧的 >60→60 硬钉)。起始**不写**背光
* (不抢播放器刚开始的写入)。 */
const char *bri_src = "?";
int bri_min = BM_BRI_MIN_DEFAULT;
int bri_user = bootmenu_settings_brightness(&bri_min); /* >0=用户设置(clamped);-1=不可读 */
int bri_panel = read_brightness();
int bri_max = read_max_brightness();
int saved_brightness = bm_bri_pick_saved(bri_user, bri_panel, bri_min, bri_max, &bri_src);
fprintf(stderr, "supervisor: player pid=%d, saved brightness=%d, monitoring...\n",
player_pid, saved_brightness);
/* 信号记录 hook(M2 诊断):先装 handler、start 一行写入 TF 卡日志。
@@ -1207,12 +1329,16 @@ static void bootmenu_supervisor(int player_pid)
bm_sig_install();
bm_log("supervisor: start pid=%d saved_brightness=%d mode=hiby ver=%s",
player_pid, saved_brightness, MEOW_BOOTMENU_VER);
bm_log("supervisor: bri saved=%d src=%s user=%d panel=%d min=%d max=%d",
saved_brightness, bri_src, bri_user, bri_panel, bri_min, bri_max);
/* Bug 4:HibyOS 开机媒体扫描窗内做亮度保底。播放器忙(媒体扫描)时其背光
* 待机会把亮度降到 0、面板 blank → 黑屏几分钟。supervisor 在扫描窗内若读到
* 亮度 0,写回保底亮度(saved 或 60)。**只写 brightness、绝不碰 FBIOBLANK**
* (不抢播放器的面板归属语义,参考 §18.30 面板归属)。扫描窗结束即放手。 */
* 待机会把亮度降到 0、面板 blank → 黑屏几分钟。**只写 brightness、绝不碰 FBIOBLANK**
* (不抢播放器的面板归属语义,参考 §18.30 面板归属)。扫描窗结束即放手。
* M23:补的值改为用户设置(不再 60),并加「播放器忙 + 面板未下电」两门(见
* bm_bri_floor_decide)—— 用户主动熄屏不再被强行点亮。 */
unsigned long long scan_keep_until = now_ms() + SCAN_KEEP_MS;
unsigned long long last_bri_floor_at = 0; /* 亮度保底节流:≥400ms 才再写一次 */
unsigned long long last_bri_floor_at = 0; /* 亮度保底节流:≥400ms 才判定/写一次 */
const char *last_floor_why = "init"; /* 只在判定原因跃迁时打日志(控日志量) */
struct keypoll kp_power, kp_keys;
if (kp_open(&kp_power, DEV_POWER) != 0) fprintf(stderr, "supervisor: WARN no power dev\n");
@@ -1268,16 +1394,35 @@ static void bootmenu_supervisor(int player_pid)
}
}
/* 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);
/* 2a) Bug 4 亮度保底(M23 重做):扫描窗内、播放器**忙**(媒体扫描)且亮度被降到 0
* → 写回**用户设置**亮度(不再 60)。门条件见 bm_bri_floor_decide:
* 窗口结束 / 面板已亮 / 播放器不忙(用户主动熄屏)/ 面板已下电(bl_power>0)→ 不写。
* 旧实现只判「亮度<=0」,用户按电源熄屏后 400ms 内被写回 60 → 熄屏瞬间爆亮。
* (播放器扫描忙时无法响应唤醒,我们只把背光拉回,画面仍为播放器最后帧;不碰 blank。)
* 判定按 400ms 节流(原写入节流同粒度),只在原因跃迁时打日志,避免刷卡。 */
if (player_alive && scan_keep_until) {
unsigned long long now2 = now_ms();
if (now2 - last_bri_floor_at >= 400) {
long long win_left = (scan_keep_until > now2)
? (long long)(scan_keep_until - now2) : 0;
int bri = read_brightness();
int busy = proc_is_busy(player_pid);
int blp = read_file_int(BACKLIGHT_POWER);
const char *why = "?";
int floor = bm_bri_floor_decide(saved_brightness, bri, busy, blp, win_left, &why);
last_bri_floor_at = now2;
if (floor > 0) {
write_brightness(floor);
/* 保留 M2 时代原有的 stderr 行(串口调试仍看它),另加一路 bm_log 落卡。 */
fprintf(stderr, "supervisor: scan-window brightness floor -> %d\n", floor);
}
if (strcmp(why, last_floor_why) != 0) {
last_floor_why = why;
bm_log("supervisor: scan-window brightness %s -> %d (user=%d panel=%d busy=%d bl_power=%d left=%lldms)",
(floor > 0) ? "floor" : "skip", floor, bri_user, bri, busy, blp, win_left);
}
/* 窗口结束 → 彻底放手:此后不再读背光/进程态,绝不与播放器抢面板。 */
if (win_left <= 0) scan_keep_until = 0;
}
}
@@ -1338,10 +1483,16 @@ static void bootmenu_supervisor(int player_pid)
prev_vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN);
continue;
}
/* 刷新 saved 亮度:若播放器当前亮度仍为正(如「背光常亮」设置),
* 以它为准;若电源按下已触发灭屏(亮度 0),保留此前 saved(≥60)。 */
/* 刷新 saved 亮度:若播放器当前亮度仍为正(如「背光常亮」设置 / 用户在 HibyOS 里
* 改过亮度但没写 settings.conf),以面板此刻的真实值为准(clamp 到
* [brightness_min, max_brightness]);若电源按下已触发灭屏(亮度 0),保留此前 saved
* —— M23 起 saved 来自 settings.conf 的用户亮度,不再回退 60。见 bm_bri_hotkey_saved。 */
int now_bri = read_brightness();
if (now_bri > 0) saved_brightness = now_bri;
const char *hk_src = "?";
saved_brightness = bm_bri_hotkey_saved(saved_brightness, now_bri,
bri_min, bri_max, &hk_src);
bm_log("supervisor: hotkey saved brightness=%d src=%s (now=%d user=%d)",
saved_brightness, hk_src, now_bri, bri_user);
/* 先冻结播放器(SIGSTOP→T),防止其在确认框上重绘覆盖;
* 同时捕获面板是否已因电源按下而灭屏(was_blank),供较后恢复。 */
if (player_alive && !proc_is_stopped(player_pid)) {
@@ -1354,7 +1505,10 @@ static void bootmenu_supervisor(int player_pid)
/* 点亮面板以便看清确认框(播放器已冻结,不会 re-blank)。 */
unblank_panel();
if (was_blank) write_brightness(saved_brightness);
if (was_blank) {
write_brightness(saved_brightness);
bm_log("supervisor: confirm-box unblank brightness=%d (panel was blank)", saved_brightness);
}
if (read_file_int(BACKLIGHT_POWER) > 0) write_file_str(BACKLIGHT_POWER, "0\n");
int ans = confirm_switch();
@@ -1409,6 +1563,7 @@ static void bootmenu_supervisor(int player_pid)
if (f >= 0) { ioctl(f, FBIOBLANK, 4); close(f); }
} else if (read_brightness() <= 0) {
write_brightness(saved_brightness);
bm_log("supervisor: cancel restore brightness=%d (panel read 0)", saved_brightness);
}
fprintf(stderr, "supervisor: hotkey cancelled (stay HibyOS)\n");
if (player_alive && proc_is_stopped(player_pid)) {
@@ -1628,7 +1783,6 @@ static int run_menu(int sim_sel, int do_fork)
/* 每秒刷时钟 */
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);
@@ -1676,6 +1830,9 @@ static void usage(void)
SIM_SELECT_DELAY_MS);
}
/* M23:宿主单测(test_bm_fw.c)先 #define BM_NO_MAIN 再把本文件 include 进来,去 main 拿
* 静态亮度决策函数(与 meow_watchdog 的 -DWD_NO_MAIN 同款);交叉编译不定义该宏,main 照常。 */
#ifndef BM_NO_MAIN
int main(int argc, char **argv)
{
long dumpkeys = 0, timeout_override = 0;
@@ -1737,10 +1894,17 @@ int main(int argc, char **argv)
}
/* M6.25(方案A):选单显示前把背光按用户设置恢复(display.brightness,clamp [min||1,100])。
* settings 不可读/值非法 → 不动(退化为现状)。只在显示前写一次,不碰 FBIOBLANK/blank(§18.24)。 */
* settings 不可读/值非法 → 不动(退化为现状)。只在显示前写一次,不碰 FBIOBLANK/blank(§18.24)。
* M23:这句在 0.8.46/0.8.49 的**出货二进制里根本不存在**(源码改了但二进制没重编,见文件头),
* 所以选单一直亮在面板继承值上。现在补一行 bm_log:每次开机留下「选单亮度到底写了多少」。 */
{
int b = bootmenu_settings_brightness();
if (b > 0) write_brightness(b);
int b = bootmenu_settings_brightness(NULL);
if (b > 0) {
write_brightness(b);
bm_log("menu: brightness -> %d (src=settings.conf)", b);
} else {
bm_log("menu: brightness untouched, settings unreadable (panel=%d)", read_brightness());
}
}
if (fb_open() != 0) return 1;
@@ -1750,3 +1914,6 @@ int main(int argc, char **argv)
run_menu(sim_sel, do_fork);
return 0;
}
/* BM_NO_MAIN(宿主单测)到此为止 */
#endif
+255
View File
@@ -0,0 +1,255 @@
/* test_bm_fw.c — 宿主单测:meow_bootmenu 的亮度决策(M23 亮度单一权威源)。
*
* 覆盖真机 0.8.46 两个症状的判定矩阵:
* ① 开机菜单/选单亮度 = 面板继承值 60(用户设置 1)—— 选单路径 bootmenu_settings_brightness()
* ② HibyOS 亮/灭屏瞬间爆亮 —— supervisor 起始 saved、扫描窗保底、热键熄屏恢复三处决策
*
* 编译(build_bootmenu.sh host;与 meow_watchdog 的 test_wd_fw.c 同款 include 打法):
* gcc -Wall -Wextra -Os -I../meow_launcherd -DBM_NO_MAIN \
* -Wno-unused-function -Wno-unused-parameter -Wno-unused-variable \
* test_bm_fw.c ../meow_launcherd/meow_touch.c -o test_bm_fw
* 运行:./test_bm_fw;全 PASS -> exit 0。
* env MEOW_BACKLIGHT / MEOW_SETTINGS_FILE 把背光与 settings 指到临时文件(真机无这两个 env,
* 走 /sys 与 /usr/data 默认路径),因此这里的读写断言就是真机同一条代码路径。
*/
#define BM_NO_MAIN
#include "meow_bootmenu.c"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static int g_pass = 0, g_fail = 0;
static void check(const char *name, int got, int want)
{
if (got == want) { g_pass++; printf("PASS %s\n", name); }
else { g_fail++; printf("FAIL %s: got %d want %d\n", name, got, want); }
}
/* 临时目录根:优先 $TMPDIR(Termux/Android 上 /tmp 属 shell 用户、本进程不可写),
* 否则 /tmp(Linux 构建容器)——与 test_wd_fw.c 一致。 */
static const char *tmp_root(void)
{
const char *t = getenv("TMPDIR");
return (t && t[0]) ? t : "/tmp";
}
static void put_file(const char *path, const char *body)
{
FILE *f = fopen(path, "w");
if (!f) { g_fail++; printf("FAIL cannot write %s\n", path); return; }
fputs(body, f);
fclose(f);
}
static int get_file_int(const char *path)
{
FILE *f = fopen(path, "r");
int v = -1;
if (!f) return -2;
if (fscanf(f, "%d", &v) != 1) v = -1;
fclose(f);
return v;
}
/* ---- A) supervisor 起始 saved_brightness:settings 是唯一权威 ----
* 真机 bug:settings display.brightness=1,面板被播放器写成 60 → 旧代码 saved=60(>60→60 的
* 硬钉同宗),随后扫描窗保底/熄屏恢复把 60 写回 → 亮/灭屏爆亮。新语义必须取 1。 */
static void test_pick_saved(void)
{
const char *src = NULL;
int v;
v = bm_bri_pick_saved(1, 60, 1, 101, &src); /* ← 真机症状本体 */
check("saved: settings 1 wins over panel 60", v == 1 && !strcmp(src, "settings"), 1);
v = bm_bri_pick_saved(1, 0, 1, 101, &src); /* 播放器已把面板写成 0 */
check("saved: settings 1 when panel dark", v == 1 && !strcmp(src, "settings"), 1);
v = bm_bri_pick_saved(7, 7, 1, 101, &src);
check("saved: settings 7", v == 7 && !strcmp(src, "settings"), 1);
/* settings 不可读(降级路径):沿用面板读数,夹进 [brightness_min, max_brightness](M23 新语义) */
v = bm_bri_pick_saved(-1, 60, 1, 101, &src);
check("saved: fallback keeps panel 60", v == 60 && !strcmp(src, "panel"), 1);
v = bm_bri_pick_saved(-1, 80, 1, 101, &src); /* 用户真设 80:不再被旧 `>60→60` 顶成 60 */
check("saved: fallback keeps 80 (old code pinned 60)", v == 80 && !strcmp(src, "panel"), 1);
v = bm_bri_pick_saved(-1, 200, 1, 101, &src); /* 越界读数夹到 max_brightness */
check("saved: fallback clamps 200 -> max 101", v == 101 && !strcmp(src, "panel"), 1);
v = bm_bri_pick_saved(-1, 37, 1, 32, &src); /* 面板上限 32 时按 32 */
check("saved: fallback clamps to max_brightness 32", v == 32 && !strcmp(src, "panel"), 1);
v = bm_bri_pick_saved(-1, 2, 6, 101, &src); /* 低于 brightness_min 时抬到 min(防黑屏) */
check("saved: fallback clamps up to brightness_min 6", v == 6 && !strcmp(src, "panel"), 1);
v = bm_bri_pick_saved(0, 3, 1, 101, &src); /* 非正值按住「未知」处理 */
check("saved: user<=0 treated unknown", v == 3 && !strcmp(src, "panel"), 1);
/* settings 与面板都不可知且面板黑:安全低亮,绝不再写 60 */
v = bm_bri_pick_saved(-1, 0, 1, 101, &src);
check("saved: dark + no settings -> safe-low", v == BM_BRI_SAFE_LOW && v != 60 &&
v > 0 && !strcmp(src, "safe-low"), 1);
v = bm_bri_pick_saved(-1, -1, 1, 0, &src); /* 读失败 + max 读不到 */
check("saved: read-fail -> safe-low", v == BM_BRI_SAFE_LOW && !strcmp(src, "safe-low"), 1);
/* max 读不到(0)时只夹下限,不臆造上限 */
v = bm_bri_pick_saved(-1, 90, 1, 0, &src);
check("saved: unknown max keeps 90", v == 90 && !strcmp(src, "panel"), 1);
}
/* ---- A2) clamp 语义单测:取代旧的 `>60 → 60` 硬钉 ---- */
static void test_clamp(void)
{
check("clamp: in-range untouched", bm_bri_clamp(37, 1, 101), 37);
check("clamp: above max -> max", bm_bri_clamp(200, 1, 101), 101);
check("clamp: 80 stays 80 (was pinned 60)", bm_bri_clamp(80, 1, 101), 80);
check("clamp: below min -> min", bm_bri_clamp(0, 3, 101), 3);
check("clamp: mn<1 -> 1", bm_bri_clamp(0, 0, 101), 1);
check("clamp: unknown max only floors", bm_bri_clamp(90, 1, 0), 90);
}
/* ---- B) 扫描窗保底:只在「播放器忙 + 面板暗 + 面板未下电」时补,补的是用户值 ---- */
static void test_floor_decide(void)
{
const char *why = NULL;
int v;
v = bm_bri_floor_decide(1, 0, 1, 0, 5000, &why); /* settings=1 + 扫描忙把面板写 0 */
check("floor: writes user value 1 (not 60)", v == 1 && !strcmp(why, "scan-floor"), 1);
v = bm_bri_floor_decide(80, 0, 1, 0, 5000, &why); /* 降级路径 saved=80 照写 80(旧码钉 60) */
check("floor: degraded saved 80 floors at 80", v == 80 && !strcmp(why, "scan-floor"), 1);
v = bm_bri_floor_decide(0, 0, 1, 0, 5000, &why); /* saved 未知 -> 安全低亮 */
check("floor: unknown saved -> safe-low", v == BM_BRI_SAFE_LOW && !strcmp(why, "scan-floor"), 1);
v = bm_bri_floor_decide(1, 5, 1, 0, 5000, &why); /* 面板已亮:不动 */
check("floor: visible -> no write", v == 0 && !strcmp(why, "visible"), 1);
v = bm_bri_floor_decide(1, 0, 0, 0, 5000, &why); /* ← 用户主动熄屏(播放器不忙):不动 */
check("floor: user screen-off (idle) -> no write", v == 0 && !strcmp(why, "idle-blank"), 1);
v = bm_bri_floor_decide(1, 0, 1, 1, 5000, &why); /* 面板已下电(bl_power>0):不动 */
check("floor: panel powered off -> no write", v == 0 && !strcmp(why, "panel-off"), 1);
v = bm_bri_floor_decide(1, 0, 1, 0, 0, &why); /* 90s 窗口结束:放手 */
check("floor: window over -> no write", v == 0 && !strcmp(why, "window-over"), 1);
v = bm_bri_floor_decide(1, 0, 1, 0, -1, &why);
check("floor: negative window -> no write", v == 0 && !strcmp(why, "window-over"), 1);
}
/* ---- C) 热键冻结前的熄屏恢复值:面板真实值 > settings saved > 安全低亮 ---- */
static void test_hotkey_saved(void)
{
const char *src = NULL;
int v;
v = bm_bri_hotkey_saved(1, 0, 1, 101, &src); /* 电源已熄屏:恢复用户值 1,不再回 60 */
check("hotkey: blank -> saved (settings 1)", v == 1 && !strcmp(src, "saved"), 1);
v = bm_bri_hotkey_saved(60, 0, 1, 101, &src); /* 降级 saved=60(settings 不可读)时才用 60 */
check("hotkey: blank -> degraded saved 60", v == 60 && !strcmp(src, "saved"), 1);
v = bm_bri_hotkey_saved(1, 77, 1, 101, &src); /* 面板亮着:跟随面板(HibyOS 里改过亮度) */
check("hotkey: panel-now wins", v == 77 && !strcmp(src, "panel-now"), 1);
v = bm_bri_hotkey_saved(1, 200, 1, 101, &src); /* 越界值 clamp 到 max_brightness */
check("hotkey: panel-now clamped to max", v == 101 && !strcmp(src, "panel-now"), 1);
v = bm_bri_hotkey_saved(1, 1, 6, 101, &src); /* 低于 brightness_min 抬到 min */
check("hotkey: panel-now raised to min 6", v == 6 && !strcmp(src, "panel-now"), 1);
v = bm_bri_hotkey_saved(0, 0, 1, 101, &src);
check("hotkey: nothing known -> safe-low", v == BM_BRI_SAFE_LOW && !strcmp(src, "safe-low"), 1);
}
/* ---- D) settings.conf 解析(与 watchdog 同源同 clamp)+ 打开重试 ---- */
static void test_settings_parse(void)
{
char path[600];
snprintf(path, sizeof path, "%s/bmsetXXXXXX", tmp_root());
int fd = mkstemp(path);
if (fd < 0) { g_fail++; printf("FAIL settings: mkstemp\n"); return; }
close(fd);
setenv("MEOW_SETTINGS_FILE", path, 1);
put_file(path, "# meow-settings 1\ndisplay.brightness = 1\ndisplay.brightness_min = 1\n"
"audio.volume = 10\n");
check("settings: device file -> 1", bootmenu_settings_brightness(NULL), 1);
put_file(path, "display.brightness = 0\ndisplay.brightness_min = 3\n");
int mn = 0;
check("settings: clamp up to min", bootmenu_settings_brightness(&mn), 3);
check("settings: brightness_min parsed", mn, 3);
put_file(path, "display.brightness = 200\ndisplay.brightness_min = 1\n");
check("settings: clamp down to 100", bootmenu_settings_brightness(NULL), 100);
put_file(path, "audio.volume = 10\n");
check("settings: missing key -> 100", bootmenu_settings_brightness(NULL), 100);
unlink(path);
unsigned long long t0 = now_ms();
int r = bootmenu_settings_brightness(NULL);
unsigned long long dt = now_ms() - t0;
check("settings: missing -> -1", r, -1);
check("settings: open retried (>=15ms)", dt >= 15, 1);
unsetenv("MEOW_SETTINGS_FILE");
}
/* ---- E) 端到端:真的读写一个「背光文件」,复现两个真机场景 ----
* 1) settings=1、播放器把面板写成 60 → supervisor 起始 saved 必须是 1,扫描窗保底写 1(不是 60)
* 2) 用户按电源熄屏(播放器不忙)→ 保底决策返回 0,面板保持 0(不被抢亮 = 熄屏不爆亮) */
static void test_e2e_backlight(void)
{
char bl[600], st[600];
snprintf(bl, sizeof bl, "%s/bmblXXXXXX", tmp_root());
snprintf(st, sizeof st, "%s/bmstXXXXXX", tmp_root());
int fd = mkstemp(bl);
if (fd < 0) { g_fail++; printf("FAIL e2e: mkstemp bl\n"); return; }
close(fd);
fd = mkstemp(st);
if (fd < 0) { g_fail++; printf("FAIL e2e: mkstemp st\n"); unlink(bl); return; }
close(fd);
setenv("MEOW_BACKLIGHT", bl, 1);
setenv("MEOW_SETTINGS_FILE", st, 1);
put_file(st, "display.brightness = 1\ndisplay.brightness_min = 1\n");
put_file(bl, "60\n"); /* 播放器启动画面写的 60 */
int mn_e2e = 1;
int user = bootmenu_settings_brightness(&mn_e2e);
int panel = read_brightness();
const char *src = "?";
int saved = bm_bri_pick_saved(user, panel, mn_e2e, 101, &src);
check("e2e: panel file read 60", panel, 60);
check("e2e: saved from settings = 1", saved, 1);
/* 扫描窗:播放器忙把背光写 0 → 保底写用户值 1 */
put_file(bl, "0\n");
const char *why = NULL;
int floor = bm_bri_floor_decide(saved, read_brightness(), 1, 0, 5000, &why);
if (floor > 0) write_brightness(floor);
check("e2e: scan floor writes 1 (not 60)", get_file_int(bl), 1);
/* 用户主动熄屏(不忙)→ 保底不写,面板保持 0 */
put_file(bl, "0\n");
floor = bm_bri_floor_decide(saved, read_brightness(), 0, 0, 5000, &why);
if (floor > 0) write_brightness(floor);
check("e2e: user screen-off stays dark", get_file_int(bl), 0);
/* 热键确认框:面板黑着 → 按 saved(用户值 1)点亮,不是 60 */
int hk = bm_bri_hotkey_saved(saved, read_brightness(), mn_e2e, 101, &src);
write_brightness(hk);
check("e2e: confirm box lights at 1", get_file_int(bl), 1);
/* settings 被改:saved 不再跟旧值(每次开机重读 settings) */
put_file(st, "display.brightness = 5\ndisplay.brightness_min = 1\n");
src = "?";
saved = bm_bri_pick_saved(bootmenu_settings_brightness(NULL), read_brightness(), mn_e2e, 101, &src);
check("e2e: saved follows new settings 5", saved, 5);
unlink(bl); unlink(st);
unsetenv("MEOW_BACKLIGHT");
unsetenv("MEOW_SETTINGS_FILE");
}
int main(void)
{
test_pick_saved();
test_clamp();
test_floor_decide();
test_hotkey_saved();
test_settings_parse();
test_e2e_backlight();
printf("\n%d passed, %d failed\n", g_pass, g_fail);
return g_fail ? 1 : 0;
}