feat: M6.44+M6.45 收编 — 真机 0.8.45 现场保全(reader章节/key_router按键路由/WiFi连接修复/ncm bench+IME修复)

脏工作区现场收编:devdocs/local/m6_44_45_dirty_tracked.patch(46 个已跟踪文本文件,
+3090/-585, 已对 M6.43 基线干净适配) + m6_44_45_untracked_src.tar.gz 中 patch_v2/ 下
13 个新增源文件, 合成单个提交, 内容与容器工作树(R3ProII/patch_v2)逐字节一致。

- reader 章节: 新增 core/rdchapter.{h,c} — TXT 章节规范解析器, 扫描 512KB 码位对齐段文件
  逐段流式识别标题行(行首缩进≤4/显示宽≤40/行内标点≤1 三重约束防误判), 输出标题+全书字符
  偏移+段号/段内偏移+阿拉伯章号(中文数字归一), 首章前内容归"卷首"; book/reader/rdenc/
  rdlayout/settings 接入章节跳转与进度锚点, 附 chapter_smoke/turn_repro/build_turn_repro.sh
- key_router 按键路由: 新增 meow_launcherd/key_router.{h,c} — launcher-UI 页物理键统一路由
  (P1 仅音量键), 把读取边沿+防抖+长按 held+ramp 推进从 5 个会话收敛为进程内单开;
  各页每圈 key_router_pump() 取防抖后边沿, 动作(调音量/光标导航)仍由调用页决定,
  保留 run_menu 的 meow_mode 分支; App 前台 volume_page 武装时边沿抑制;
  main_menu/now_playing/notif_center/settings_page/subpage 接入, 附 test_key_router_vol/test_np_keys
- WiFi 连接修复: meow_netmgrd connect 有界等待 wpa_state=COMPLETED(普通 10s/隐藏网络 15s,
  env 可调)修设置页连接无响应; list_networks/scan_results 的 \xNN 转义还原始字节, 修中文 SSID
  永不命中致新建同名 open 记录顶影; launcher netmgr_ipc 配读超时; 附 test_wifi_connect(桩
  wpa_cli 驱动 static wifi_connect_ex), test_netmgrd.sh 补用例; wifi_page 同步
- ncm bench: 新增 bench/lyric_sim.c、bench/plpage_sim.c, vol_sync_sim 与 ncm core/app 同步
- SDK IME 修复: shift 大小写、K_ENTER 只提交不关、阻塞 IME 循环喂 on_tick 不被遮挡、
  IME 打开 ~200ms TAP 冷却窗吞 stray-TAP; 附 sdk/test_ime_fix.c
- 同期: meow_audiod 音量异步 ACK 线程(代际防 fd/槽复用竞态)、watchdog 音量收编(首选 audiod
  权威, settings.conf 覆盖路径)、panel 文件搜索(F1-④)与蓝牙只读详情(F1-③)、各类 build 脚本同步

范围红线: third_party/legado-E-main 原开发者未跟踪, 本次不入库(保留 devdocs/local tar 参考);
二进制不入库(后续重编); devdocs/ 不入库。59 文件全部在 patch_v2/ 下, 无 third_party/devdocs。
验证: git apply --check 干净; 59 文件 md5 与容器 patch_v2 逐一比对 59/59 一致(含
key_router.c/rdchapter.c/meow_ui_ime.c/test_wifi_connect.c 抽查); 可执行位保守
(check_lgpl_release.sh 100755, build_turn_repro.sh 100644 与容器一致)。
This commit is contained in:
AskaEth committed 2026-11-04 20:02:28 +08:00
1 parent e87ccdf061
commit 7c7d891570
59 files changed
+5305 -590

No files matched your search

@@ -0,0 +1,229 @@
/* test_key_router_vol.c — 宿主单测:launcher-UI 页物理键统一路由(KEY_ROUTER_UNIFY P1)。
*
* 测「key_router 自身的音量编排」——它只产「音量边沿 + 原始 held + ramp 状态」,
* 由调用页决定 settings_volume_adj(±step) / 光标导航 / ramp 推进。本测用内存桩:
* - kp_poll/kp_open/kp_is_down → 注入 raw 键位矩阵到 key_router 的静态 kp_keys.cur[];
* - kd_tick → 用 keys.c 的真实现(40ms 防抖,V4 依赖);keys.c 内 kp_open/kp_poll/
* kp_is_down/meow_volpage_armed 用宏重命名避开真实 ioctl 与桩冲突;
* - settings_volume_adj/settings_volume_step/vol_ramp_dir/vol_ramp_tick → 内存桩;
* - now_ms → 假时钟(手动推进,40ms 防抖可瞬时测);
* - meow_ui_activity_* → 内存桩(V5 嵌套:真语义=纯静态数组,桩复刻)。
*
* 断言:
* V1 音量减边沿 → settings_volume_adj(-step) 恰一次 + last_input 复位;
* V2 同会话重复(同 raw 仍按住)→ 不再触发 settings_volume_adj;
* V3 长按 held → vol_ramp_tick 被驱动(vol_ramp_dir!=0,held=正确方向 raw);松开 → dir=0;
* V4 40ms 防抖:raw 在 40ms 内抖动 → 不产边沿;稳定按下 ≥40ms → 才产边沿;
* V5 嵌套返回:page_enter(0x53)→page_exit(0x53)→page_enter(0x55),基线重播后 held 键不产假边沿;
* V6 meow_volpage_armed() → 音量边沿被抑制(不下发,不调 settings_volume_adj)。
*
* 编译(在 patch_v2/meow_launcherd 下):
* gcc -Wall -Wextra -I../sdk -I. -ffunction-sections -fdata-sections \
* -Wl,--gc-sections test_key_router_vol.c -o test_key_router_vol
* ./test_key_router_vol
*/
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdint.h>
/* ---- 假时钟 ---- */
static unsigned long long g_now = 100000;
unsigned long long now_ms(void) { return g_now; }
static void advance_ms(unsigned long long d) { g_now += d; }
/* ---- 音量机外部符号桩(key_router 依赖)---- */
int settings_volume_step = 5;
static int g_adj_count = 0, g_adj_delta = 0;
int vol_ramp_dir = 0; /* 桩全局:模拟 audio.c 的 ramp 状态 */
static int g_tick_count = 0, g_tick_held = -1;
void settings_volume_adj(int delta)
{
g_adj_count++; g_adj_delta = delta;
if (delta > 0) vol_ramp_dir = 1; else if (delta < 0) vol_ramp_dir = -1;
}
int vol_ramp_tick(int key_held)
{
g_tick_count++; g_tick_held = key_held;
if (!key_held) vol_ramp_dir = 0;
return vol_ramp_dir != 0;
}
static int g_volpage = 0; /* 桩:meow_volpage_armed */
/* ---- keys.c 内 kp_open/kp_poll/kp_is_down/meow_volpage_armed 用宏重命名,避开真实 ioctl ----
* (宏在 #include keys.c 前生效,仅重命名 keys.c 体内的调用/定义) ---- */
#define kp_open kp_open_rt_
#define kp_poll kp_poll_rt_
#define kp_is_down kp_is_down_rt_
#define meow_volpage_armed meow_volpage_armed_rt_
#include "keys.c" /* 真 kd_tick(40ms 防抖素材) */
#undef kp_open
#undef kp_poll
#undef kp_is_down
#undef meow_volpage_armed
/* host 桩声明(key_router.c 需要;恢复到正常名,与 keys.c 被更名后的定义不冲突) */
struct keypoll;
int kp_open(struct keypoll *kp, const char *dev);
int kp_poll(struct keypoll *kp, int (*cb)(int code, int down, void *ctx), void *ctx);
int kp_is_down(const struct keypoll *k, int code);
int meow_volpage_armed(void);
#include "key_router.c" /* 同 TU #include:拿到静态 kp_keys/kp_power/kd_vold/kd_volu + 全部函数 */
#include "key_router.h"
/* ---- meow_ui_activity_* 桩(复刻 sdk/meow_ui_activity.c:纯静态数组,真语义)---- */
static int g_act[32]; static int g_act_n = 0;
void meow_ui_activity_push(int id) { if (g_act_n < 32) g_act[g_act_n++] = id; }
void meow_ui_activity_pop(void) { if (g_act_n > 0) g_act_n--; }
int meow_ui_activity_active(void) { return g_act_n > 0 ? g_act[g_act_n - 1] : 0; }
/* ---- 注入 raw 到 key_router 静态 kp_keys ---- */
static void set_vol(int vold, int volu)
{
for (int w = 0; w < KEYBUF_WORDS; w++) kp_keys.cur[w] = 0;
if (vold) kp_keys.cur[KEY_VOLUMEDOWN / 32] |= (1UL << (KEY_VOLUMEDOWN % 32));
if (volu) kp_keys.cur[KEY_VOLUMEUP / 32] |= (1UL << (KEY_VOLUMEUP % 32));
}
/* ---- host 桩:kp_*(prev=cur 模拟真实 poll 的键位更新)+ meow_volpage_armed ---- */
int kp_open(struct keypoll *kp, const char *dev)
{ (void)dev; memset(kp, 0, sizeof *kp); kp->ok = 1; return 0; }
int kp_poll(struct keypoll *kp, int (*cb)(int, int, void *), void *ctx)
{
(void)cb; (void)ctx;
for (int w = 0; w < KEYBUF_WORDS; w++) kp->prev[w] = kp->cur[w];
return 0;
}
int kp_is_down(const struct keypoll *k, int code)
{ return (k->cur[code / 32] >> (code % 32)) & 1; }
int meow_volpage_armed(void) { return g_volpage; }
/* 其它全局桩(keys.c 内被更名函数引用;本测 GC 后不保留,仅满足声明一致性) */
uint32_t menu_bg = 0;
int screen_off_timeout = 0, screen_off_mode = 0, power_longpress_menu_ms = 0;
int power_forceoff_ms = 0, refresh_rate_cfg = 60, refresh_always_max = 0;
void logmsg(const char *fmt, ...) { (void)fmt; }
static int g_fails = 0;
#define CHECK(cond, name) do { \
if (cond) printf(" PASS %s\n", name); \
else { printf(" FAIL %s (%s:%d)\n", name, __FILE__, __LINE__); g_fails++; } } while (0)
static void reset_router(void)
{
set_vol(0, 0);
key_router_rebase();
g_adj_count = 0; g_adj_delta = 0; g_tick_count = 0; g_tick_held = -1;
vol_ramp_dir = 0; g_volpage = 0;
g_act_n = 0;
}
/* 按下并稳定:register(pump) → hold ≥40ms → pump(stable=1, 边沿)。 */
static key_evt_t press_settle(int vold, int volu)
{
set_vol(vold, volu);
key_router_pump(); /* 登记 raw 跳变(防抖窗口起点) */
advance_ms(50); /* ≥KEY_DEBOUNCE_MS(40) */
return key_router_pump(); /* stable→1,边沿 */
}
/* 抬起并稳定:register(pump) → → pump(stable=0)。 */
static key_evt_t release_settle(void)
{
set_vol(0, 0);
key_router_pump(); /* 登记 release */
advance_ms(50);
return key_router_pump(); /* stable→0 */
}
int main(void)
{
unsigned long long last_input = 0;
/* ---- V1 音量减边沿 → settings_volume_adj(-step) 恰一次 + last_input 复位 ---- */
reset_router();
set_vol(0, 0); advance_ms(10); key_router_pump(); /* 全抬起基线 */
last_input = 0;
key_evt_t ev = press_settle(1, 0);
CHECK(ev.vol_dn_edge == 1, "V1 vol-down edge fires");
key_router_default_volume(&ev, &last_input);
CHECK(g_adj_count == 1 && g_adj_delta == -settings_volume_step,
"V1 vol-down -> settings_volume_adj(-step) exactly once");
CHECK(last_input == g_now, "V1 last_input reset on edge");
/* ---- V2 同会话重复(同 raw 仍按住)→ 不再触发 ---- */
reset_router();
set_vol(0, 0); advance_ms(10); key_router_pump();
ev = press_settle(1, 0);
CHECK(ev.vol_dn_edge == 1, "V2a press edge fires");
ev = key_router_pump(); /* 同 raw 仍按住,无新边沿 */
CHECK(ev.vol_dn_edge == 0, "V2 held: no re-trigger edge");
key_router_default_volume(&ev, &last_input);
CHECK(g_adj_count == 0, "V2 held: no double settings_volume_adj");
/* ---- V3 长按 held → vol_ramp_tick 被驱动(held=正确方向 raw);松开 → dir=0 ---- */
reset_router();
set_vol(0, 0); advance_ms(10); key_router_pump(); last_input = 0;
ev = press_settle(1, 0); /* 按下(vol down)并转稳定 */
key_router_default_volume(&ev, &last_input);
CHECK(g_tick_count == 1 && g_tick_held == ev.vol_held_dn && vol_ramp_dir != 0,
"V3 hold vol-down -> ramp driven with held=raw down");
advance_ms(50);
ev = key_router_pump(); /* 继续长按(raw=1 仍按住) */
key_router_default_volume(&ev, &last_input);
CHECK(g_tick_count == 2 && g_tick_held == 1, "V3 long-press continues ramp");
ev = release_settle(); /* 松开:raw held 立即为 0 */
key_router_default_volume(&ev, &last_input);
CHECK(vol_ramp_dir == 0, "V3 release -> ramp dir 0 (stops)");
/* ---- V4 40ms 防抖:raw 在 40ms 内抖动不产边沿;稳定 ≥40ms 才产 ---- */
reset_router();
set_vol(0, 0); advance_ms(10); key_router_pump();
set_vol(1, 0); advance_ms(20); key_router_pump(); /* 按下未满 40ms */
set_vol(0, 0); advance_ms(20); key_router_pump(); /* 40ms 内又抬起 */
ev = key_router_pump();
CHECK(ev.vol_dn_edge == 0, "V4 <40ms jitter -> no edge");
ev = press_settle(1, 0); /* 稳定按下 ≥40ms */
CHECK(ev.vol_dn_edge == 1, "V4 stable >=40ms press -> edge");
ev = release_settle(); /* 抬起稳定 */
CHECK(ev.vol_dn_edge == 0 && ev.vol_up_edge == 0, "V4 released -> no residual edge");
ev = press_settle(0, 1); /* 稳定按音量加 */
CHECK(ev.vol_up_edge == 1, "V4 vol-up stable press -> up edge");
/* ---- V5 嵌套返回:page_enter/exit 重播基线,held 键不产假边沿 ---- */
reset_router();
meow_ui_activity_push(0x55); key_router_page_enter(0x55); /* 主菜单 */
set_vol(0, 0); advance_ms(10); key_router_pump();
ev = press_settle(1, 0);
CHECK(ev.vol_dn_edge == 1, "V5a press edge in menu");
/* 嵌套进设置页:压栈 + 登记 */
meow_ui_activity_push(0x53); key_router_page_enter(0x53);
CHECK(meow_ui_activity_active() == 0x53, "V5b settings active sentinel");
ev = release_settle(); /* 设置页内把键抬起 */
CHECK(ev.vol_dn_edge == 0, "V5c settings-up: no fresh edge (consumer)");
/* 设置页返回(模拟 run_settings_page 结束):弹栈 + 重播基线 → m_prev=当前 raw(0) */
key_router_page_exit(0x53); meow_ui_activity_pop();
key_router_page_enter(0x55);
CHECK(meow_ui_activity_active() == 0x55, "V5d menu active again");
/* 主菜单基线已重播;再次按下应为新边沿,而非残留/双发 */
ev = press_settle(1, 0);
CHECK(ev.vol_dn_edge == 1, "V5e fresh press after nest return -> single edge");
/* ---- V6 meow_volpage_armed() → 音量边沿被抑制(不下发)---- */
reset_router();
set_vol(0, 0); advance_ms(10); key_router_pump();
g_volpage = 1;
ev = press_settle(1, 0);
CHECK(ev.vol_dn_edge == 0, "V6 volpage armed -> edge suppressed");
key_router_default_volume(&ev, &last_input);
CHECK(g_adj_count == 0, "V6 volpage armed -> settings_volume_adj NOT called");
g_volpage = 0;
printf("\nRESULT: %s (%d fails)\n", g_fails ? "FAIL" : "ALL PASS", g_fails);
return g_fails ? 1 : 0;
}