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

+154
View File
@@ -175,6 +175,160 @@ int api_net_dns_flush(int sock)
return rc;
}
/* ---- F1-③ 蓝牙只读详情(FUTURE_MEOWPANEL §2.6):netmgrd MSG_NET_BT_CTL(0xE6) 读级五命令
* (status/paired/connected/a2dp/devices)聚合为一份 JSON。读级任意调用者(bt_cmd_is_write=读),
* 无阻塞(net_ipc 2s 超时兜底);子命令失败/坏字节 → 该子块空数组降级,绝不拖垮整卡、绝不发非法 JSON。 ---- */
/* 单发 0xE6 读命令;成功返回文本长度(>0),IPC/服务端失败 -1,坏 UTF-8 -2(该子块降级)。 */
static int bt_ctl_read(const char *cmd, char *out, size_t cap)
{
m_net_cmd_t q; memset(&q, 0, sizeof q);
snprintf(q.cmd, sizeof q.cmd, "%s", cmd);
uint16_t t; uint32_t code;
int n = net_ipc(MSG_NET_BT_CTL, &q, sizeof q, &t, &code, NULL, out, cap);
if (n <= 0 || t != MSG_NET_BT_RESULT || code != MEOW_OK) return -1;
if (!utf8_valid_n(out, (size_t)n)) return -2;
return n;
}
static int bt_mac_ok(const char *m)
{
for (int i = 0; i < 17; i++) {
if (i % 3 == 2) { if (m[i] != ':') return 0; }
else if (!is_hex(m[i])) return 0;
}
return 1;
}
/* "Device <MAC> <Name>" 行集 → JSON 数组项(不合法行跳过;名经 sA_str_n 消毒)。返回条数。 */
static int bt_devlines_json(sbuf_t *s, const char *txt, int max)
{
int n = 0;
const char *ln = txt;
while (ln && *ln && n < max) {
const char *nl = strchr(ln, '\n');
size_t ll = nl ? (size_t)(nl - ln) : strlen(ln);
const char *p = ln;
while ((size_t)(p - ln) < ll && (*p == ' ' || *p == '\t')) p++;
if ((size_t)(ln + ll - p) >= 7 + 17 && !strncmp(p, "Device ", 7) && bt_mac_ok(p + 7)) {
const char *mac = p + 7;
const char *nm = mac + 17;
while ((size_t)(nm - ln) < ll && (*nm == ' ' || *nm == '\t')) nm++;
size_t nlen = ll - (size_t)(nm - ln);
while (nlen && (nm[nlen - 1] == ' ' || nm[nlen - 1] == '\t' || nm[nlen - 1] == '\r')) nlen--;
if (n) sA(s, ",");
sA(s, "{\"mac\":");
sA_str_n(s, mac, 17);
sA(s, ",\"name\":");
sA_str_n(s, nm, nlen);
sA(s, "}");
n++;
}
ln = nl ? nl + 1 : NULL;
}
return n;
}
/* 行内 " key=value"(空白/行尾收尾)取值。 */
static void bt_line_kv(const char *line, const char *key, char *out, size_t cap)
{
out[0] = 0;
const char *v = strstr(line, key);
if (!v) return;
v += strlen(key);
size_t i = 0;
while (v[i] && v[i] != ' ' && v[i] != '\t' && v[i] != '\n' && v[i] != '\r' && i + 1 < cap) { out[i] = v[i]; i++; }
out[i] = 0;
}
/* "A2DP <mac> codec=.. rate=.. channels=.. bits=.. acquired=<0|1> role=<..>" 行集 → JSON 数组项。 */
static int bt_a2dp_json(sbuf_t *s, const char *txt, int max)
{
int n = 0;
const char *ln = txt;
while (ln && *ln && n < max) {
const char *nl = strchr(ln, '\n');
size_t ll = nl ? (size_t)(nl - ln) : strlen(ln);
char line[320];
if (ll >= sizeof line) ll = sizeof line - 1;
memcpy(line, ln, ll); line[ll] = 0;
if (!strncmp(line, "A2DP ", 5) && strncmp(line, "A2DP none", 9) && bt_mac_ok(line + 5)) {
char codec[24], rate[16], chn[8], bits[8], acq[8], role[8];
bt_line_kv(line, " codec=", codec, sizeof codec);
bt_line_kv(line, " rate=", rate, sizeof rate);
bt_line_kv(line, " channels=", chn, sizeof chn);
bt_line_kv(line, " bits=", bits, sizeof bits);
bt_line_kv(line, " acquired=", acq, sizeof acq);
bt_line_kv(line, " role=", role, sizeof role);
if (n) sA(s, ",");
sA(s, "{\"mac\":");
sA_str_n(s, line + 5, 17);
sA(s, ",\"codec\":"); sA_str(s, codec);
sA(s, ",\"rate\":"); sA_str(s, rate);
sA(s, ",\"channels\":"); sA_str(s, chn);
sA(s, ",\"bits\":"); sA_str(s, bits);
sA(s, ",\"acquired\":%s", !strcmp(acq, "1") ? "true" : "false");
sA(s, ",\"role\":"); sA_str(s, role);
sA(s, "}");
n++;
}
ln = nl ? nl + 1 : NULL;
}
return n;
}
/* 五读文本聚合(任一 NULL=该子块空数组;st=NULL → {"available":false})。 */
int net_bt_aggregate(const char *st, const char *paired, const char *connected,
const char *a2dp, const char *devices, sbuf_t *s)
{
if (!st) { sA(s, "{\"available\":false}"); return -1; }
int avail = 0, powered = 0; long long paired_n = -1, connected_n = -1;
char addr[24] = "";
(void)sscanf(st, "bt available=%d powered=%d address=%23s paired=%lld connected=%lld",
&avail, &powered, addr, &paired_n, &connected_n);
if (!strcmp(addr, "-")) addr[0] = 0;
sA(s, "{\"available\":%s", avail ? "true" : "false");
sA(s, ",\"powered\":%s", powered ? "true" : "false");
sA(s, ",\"address\":"); sA_str(s, addr);
if (paired_n >= 0) sA(s, ",\"paired_n\":%lld", paired_n); else sA(s, ",\"paired_n\":null");
if (connected_n >= 0) sA(s, ",\"connected_n\":%lld", connected_n); else sA(s, ",\"connected_n\":null");
sA(s, ",\"paired\":["); if (paired) bt_devlines_json(s, paired, 32); sA(s, "]");
sA(s, ",\"connected\":["); if (connected) bt_devlines_json(s, connected, 16); sA(s, "]");
sA(s, ",\"a2dp\":["); if (a2dp) bt_a2dp_json(s, a2dp, 8); sA(s, "]");
sA(s, ",\"nearby\":["); if (devices) bt_devlines_json(s, devices, 16); sA(s, "]");
sA(s, "}");
return 0;
}
int api_net_bt(int sock)
{
char st[MEOW_IPC_MAX], pa[MEOW_IPC_MAX], co[MEOW_IPC_MAX], a2[MEOW_IPC_MAX], dv[MEOW_IPC_MAX];
int ns = bt_ctl_read("status", st, sizeof st);
if (ns <= 0) /* netmgrd 不可达/status 坏字节 → 整体降级(net 域惯例:200 available:false) */
return send_json(sock, 200, "{\"available\":false}");
int np = bt_ctl_read("paired", pa, sizeof pa);
int nc = bt_ctl_read("connected", co, sizeof co);
int na = bt_ctl_read("a2dp", a2, sizeof a2);
int nd = bt_ctl_read("devices", dv, sizeof dv);
sbuf_t s = {0};
net_bt_aggregate(st, np > 0 ? pa : NULL, nc > 0 ? co : NULL, na > 0 ? a2 : NULL, nd > 0 ? dv : NULL, &s);
/* 放行前自校验(与 overview 同口径:浏览器 JSON.parse 不容忍尾随垃圾/括号错位/内嵌 NUL/坏字节) */
jv_t *chk = jv_parse(s.b ? s.b : "{}", (int)(s.b ? s.n : 2));
int bad = !s.b || !chk || (strlen(s.b) != s.n) ||
!utf8_valid_n(s.b, s.n) || !json_strict_balanced(s.b, s.n);
if (chk) jv_free(chk);
if (bad) {
FILE *df = fopen("/tmp/net_bt_bad.json", "w");
if (df && s.b) { fwrite(s.b, 1, s.n, df); fclose(df); }
panel_log("net/bt: SELF-CHECK fail, raw dumped /tmp/net_bt_bad.json");
sbuf_free(&s);
return send_err(sock, 500, "INTERNAL", "蓝牙聚合输出未通过 JSON 自校验(原始数据已留 /tmp/net_bt_bad.json)");
}
int rc = send_json(sock, 200, s.b);
sbuf_free(&s);
return rc;
}
int api_net_log(int sock)
{
if (!tf_present()) return send_err(sock, 503, "NO_CARD", "未识别到 TF 卡");