/* test_bt_page.c — MeowOS launcher 蓝牙子页(bt_page.c)sub_page_t 逻辑宿主自测(非生产代码)。 * * 通过 #include bt_page.c 把其 FILE-STATIC 的 bt_top_sw/bt_rows/bt_act/bt_detail 平移进本 TU, * 桩掉 netmgr IPC 客户端(nm_bt_*)+ 设置后端(meow_settings_*)+ UI(s_dd_modal/logmsg 等), * 直接验证: * P1 bt_rows 行骨架(plan §六 8 分组:②A2DP 摘要 ③输出目标 ④已连接 ⑤已配对 ⑥附近 ⑦音质 ⑧功耗) * 与「已连接设备」数据(权威 netmgrd 状态 → mac/name/codec 渲染); * P2 bt_act 命中 → 正确下发 netmgrd 命令(已连接→disconnect、已配对未连→connect、输出目标 * 切换、编解码器循环); * P3 附近设备仅列未配对(已配对者归「已配对设备」)。 * * 编译(在 patch_v2/meow_launcherd 下): * gcc -I../sdk -I. -Wall -Wextra -fsanitize=undefined test_bt_page.c -o test_bt_page * 运行:./test_bt_page */ #include #include #include #include #include #include "meow_launcher_internal.h" #include "bt_page.c" /* bt_rows/bt_act/bt_top_sw/bt_detail 平移到本 TU */ /* ---------- 测试态:netmgrd BT 状态桩(launcher 客户端视角) ---------- */ static meow_net_t g_conn[8], g_paired[8], g_near[8]; static int g_conn_n, g_paired_n, g_near_n; static bt_ep_t g_eps[8]; static int g_eps_n; static char g_last_cmd[128]; /* 记录 act 下发命令,如 "disconnect:" */ static int g_output_bt; /* audio.output_bt 桩值 */ static char g_codec[16] = "auto"; /* bt.codec 桩值 */ int nm_bt_powered(void) { return 1; } int nm_bt_onoff(int on) { (void)on; return 0; } int nm_bt_connected(meow_net_t *out, int max) { if (max > g_conn_n) max = g_conn_n; memcpy(out, g_conn, (size_t)max * sizeof *out); return max; } int nm_bt_paired(meow_net_t *out, int max) { if (max > g_paired_n) max = g_paired_n; memcpy(out, g_paired, (size_t)max * sizeof *out); return max; } int nm_bt_devices(meow_net_t *out, int max) { if (max > g_near_n) max = g_near_n; memcpy(out, g_near, (size_t)max * sizeof *out); return max; } int nm_bt_a2dp(bt_ep_t *eps, int max) { if (max > g_eps_n) max = g_eps_n; memcpy(eps, g_eps, (size_t)max * sizeof *eps); return max; } static void set_cmd(const char *cmd, const char *mac) { snprintf(g_last_cmd, sizeof g_last_cmd, "%s:%s", cmd, mac ? mac : ""); } int nm_bt_connect(const char *mac) { set_cmd("connect", mac); return MEOW_OK; } int nm_bt_disconnect(const char *mac) { set_cmd("disconnect", mac); return MEOW_OK; } int nm_bt_pair(const char *mac) { set_cmd("pair", mac); return MEOW_OK; } int nm_bt_unpair(const char *mac) { set_cmd("unpair", mac); return MEOW_OK; } void nm_bt_scan_on(void) {} void nm_bt_scan_off(void) {} int netmgr_ok(void) { return 1; } void net_bt_mode_str(char *buf, int len) { snprintf(buf, (size_t)len, "主模式·已连 Pudding"); } /* ---------- 设置/UI 桩 ---------- */ int meow_settings_get_bool(const char *k, int *v) { if (v) *v = 0; if (!strcmp(k, "network.bluetooth_enabled")) { if (v) *v = 1; return 0; } if (!strcmp(k, "audio.output_bt")) { if (v) *v = g_output_bt; return 0; } return -1; } int meow_settings_set_bool(const char *k, int v) { if (!strcmp(k, "audio.output_bt")) { g_output_bt = v; return 0; } if (!strcmp(k, "network.bluetooth_enabled")) return 0; return -1; } int meow_settings_get_str(const char *k, char *b, int n) { if (!strcmp(k, "bt.codec")) { snprintf(b, (size_t)n, "%s", g_codec); return 0; } if (b && n > 0) b[0] = 0; return -1; } int meow_settings_set_str(const char *k, const char *v) { if (!strcmp(k, "bt.codec")) { snprintf(g_codec, sizeof g_codec, "%s", v); return 0; } return -1; } int meow_settings_save(void) { return 0; } void paint_sb_right(void) {} void logmsg(const char *fmt, ...) { (void)fmt; } void bt_role_refresh(int force) { (void)force; } meow_sys_state g_sys; int s_dd_modal(const char *title, const char *item[S_NET_MAX], int n, int cur) { (void)title; (void)item; (void)n; (void)cur; return -1; } int meow_subpage_run(sub_page_t *p) { (void)p; return 0; } static int g_fail = 0; #define CHECK(cond, ...) do { if (cond) { printf(" PASS: " __VA_ARGS__); printf("\n"); } \ else { printf(" FAIL: " __VA_ARGS__); printf("\n"); g_fail++; } } while (0) /* 在 rows 里找下标:kind==HDR 且 label==lbl;返回该 HDR 的下一行下标起查找 */ static int find_hdr(const sub_row_t *r, int n, const char *lbl) { for (int i = 0; i < n; i++) if (r[i].kind == SUB_HDR && r[i].label && !strcmp(r[i].label, lbl)) return i; return -1; } /* 在 [from,n) 找首个 SUB_ACT 且 label 前缀 == pref,返回下标 */ static int find_act(const sub_row_t *r, int n, int from, const char *pref) { for (int i = from; i < n; i++) if (r[i].kind == SUB_ACT && r[i].label && !strncmp(r[i].label, pref, strlen(pref))) return i; return -1; } static void fill_state(void) { /* 已连接:Pudding(a2dp 播放中)+ Speaker */ g_conn_n = 2; snprintf(g_conn[0].mac, sizeof g_conn[0].mac, "33:DE:2E:21:AD:30"); snprintf(g_conn[0].name, sizeof g_conn[0].name, "Pudding"); snprintf(g_conn[1].mac, sizeof g_conn[1].mac, "11:22:33:44:55:66"); snprintf(g_conn[1].name, sizeof g_conn[1].name, "Speaker"); /* 已配对:+ 17OP15(未连) */ g_paired_n = 3; memcpy(&g_paired[0], &g_conn[0], sizeof g_conn[0]); memcpy(&g_paired[1], &g_conn[1], sizeof g_conn[1]); snprintf(g_paired[2].mac, sizeof g_paired[2].mac, "E0:42:6D:79:C5:98"); snprintf(g_paired[2].name, sizeof g_paired[2].name, "17OP15"); /* 附近:Earbuds(未配对 → 附近列出) */ g_near_n = 2; snprintf(g_near[0].mac, sizeof g_near[0].mac, "AA:BB:CC:DD:EE:FF"); snprintf(g_near[0].name, sizeof g_near[0].name, "Earbuds"); snprintf(g_near[1].mac, sizeof g_near[1].mac, "33:DE:2E:21:AD:30"); /* 已配对 → 附近应过滤 */ snprintf(g_near[1].name, sizeof g_near[1].name, "Pudding"); /* A2DP:Pudding SBC/44100 acquired */ g_eps_n = 1; snprintf(g_eps[0].mac, sizeof g_eps[0].mac, "33:DE:2E:21:AD:30"); snprintf(g_eps[0].codec, sizeof g_eps[0].codec, "SBC"); snprintf(g_eps[0].rate, sizeof g_eps[0].rate, "44100"); snprintf(g_eps[0].chn, sizeof g_eps[0].chn, "2"); snprintf(g_eps[0].bits, sizeof g_eps[0].bits, "16"); g_eps[0].acquired = 1; snprintf(g_eps[0].role, sizeof g_eps[0].role, "src"); g_output_bt = 0; /* DAC */ snprintf(g_codec, sizeof g_codec, "auto"); } int main(void) { setbuf(stdout, NULL); fill_state(); bt_ctx_t c; memset(&c, 0, sizeof c); c.full = 1; c.output_bt = 0; sub_row_t rows[SUB_ROWS_MAX]; int n = bt_rows(&c, rows, SUB_ROWS_MAX); printf("== P1 bt_rows 分组骨架 ==\n"); CHECK(find_hdr(rows, n, "输出目标") >= 0, "分组③输出目标存在"); CHECK(find_hdr(rows, n, "已连接设备") > 0, "分组④已连接设备存在"); CHECK(find_hdr(rows, n, "已配对设备") > 0, "分组⑤已配对设备存在"); CHECK(find_hdr(rows, n, "附近设备") > 0, "分组⑥附近设备存在"); CHECK(find_hdr(rows, n, "音质 / 连接管理") > 0, "分组⑦音质/连接管理存在"); CHECK(find_hdr(rows, n, "功耗") > 0, "分组⑧功耗存在"); /* A2DP 摘要(首行 SUB_ROW,主=Pudding,副含 codec/采样率/输出=DAC) */ CHECK(rows[0].kind == SUB_ROW, "行0=A2DP摘要(SUB_ROW)"); CHECK(rows[0].label && !strcmp(rows[0].label, "Pudding"), "摘要主=Pudding (实际=%s)", rows[0].label ? rows[0].label : "(null)"); CHECK(rows[0].sub && strstr(rows[0].sub, "SBC") && strstr(rows[0].sub, "44100") && strstr(rows[0].sub, "输出=DAC"), "摘要副含 codec/采样率/输出=DAC (%s)", rows[0].sub ? rows[0].sub : "(null)"); printf("== P1 已连接设备(权威状态,含 codec join)==\n"); int hdr = find_hdr(rows, n, "已连接设备"); CHECK(hdr >= 0, "定位已连接 HDR"); int ci = find_act(rows, n, hdr + 1, "Pudding"); CHECK(ci >= 0, "已连接列出 Pudding"); CHECK(ci >= 0 && rows[ci].sub && strstr(rows[ci].sub, "SBC"), "已连接 Pudding sub 含 codec SBC"); CHECK(find_act(rows, n, hdr + 1, "Speaker") >= 0, "已连接列出 Speaker"); printf("== P1 已配对设备(已连者标「已连接」on=1)==\n"); hdr = find_hdr(rows, n, "已配对设备"); CHECK(hdr >= 0, "定位已配对 HDR"); ci = find_act(rows, n, hdr + 1, "Pudding"); CHECK(ci >= 0 && rows[ci].on == 1 && rows[ci].sub && strstr(rows[ci].sub, "已连接"), "已配对 Pudding 标已连接(on=1)"); ci = find_act(rows, n, hdr + 1, "17OP15"); CHECK(ci >= 0 && rows[ci].on == 0, "已配对 17OP15 未连(on=0)"); printf("== P3 附近设备仅列未配对(已配对者过滤)==\n"); hdr = find_hdr(rows, n, "附近设备"); CHECK(hdr >= 0, "定位附近 HDR"); ci = find_act(rows, n, hdr + 1, "Earbuds"); CHECK(ci >= 0, "附近列出 Earbuds"); ci = find_act(rows, n, hdr + 1, "Pudding"); CHECK(ci < 0, "已配对 Pudding 不在附近列表"); printf("== P2 bt_act 命中 → netmgrd 命令 ==\n"); /* 已连接 Pudding tap → disconnect */ memset(&c, 0, sizeof c); c.full = 1; fill_state(); n = bt_rows(&c, rows, SUB_ROWS_MAX); hdr = find_hdr(rows, n, "已连接设备"); ci = find_act(rows, n, hdr + 1, "Pudding"); g_last_cmd[0] = 0; bt_act(&c, ci, rows[ci].act); CHECK(strcmp(g_last_cmd, "disconnect:33:DE:2E:21:AD:30") == 0, "已连接 tap → disconnect (实际=%s)", g_last_cmd); /* 已配对 17OP15(未连)tap → connect */ memset(&c, 0, sizeof c); c.full = 1; fill_state(); n = bt_rows(&c, rows, SUB_ROWS_MAX); hdr = find_hdr(rows, n, "已配对设备"); ci = find_act(rows, n, hdr + 1, "17OP15"); g_last_cmd[0] = 0; bt_act(&c, ci, rows[ci].act); CHECK(strcmp(g_last_cmd, "connect:E0:42:6D:79:C5:98") == 0, "已配对未连 tap → connect (实际=%s)", g_last_cmd); /* 输出目标 toggle → audio.output_bt 翻转为 1;bt_rows 摘要改输出=蓝牙 */ memset(&c, 0, sizeof c); c.full = 1; fill_state(); n = bt_rows(&c, rows, SUB_ROWS_MAX); hdr = find_hdr(rows, n, "输出目标"); ci = find_act(rows, n, hdr + 1, "输出目标"); bt_act(&c, ci, rows[ci].act); CHECK(g_output_bt == 1, "输出目标 tap → output_bt=1 (蓝牙)"); n = bt_rows(&c, rows, SUB_ROWS_MAX); CHECK(rows[0].sub && strstr(rows[0].sub, "输出=蓝牙"), "切蓝牙后 A2DP 摘要=输出=蓝牙"); /* 编解码器循环:auto → SBC */ memset(&c, 0, sizeof c); c.full = 1; fill_state(); n = bt_rows(&c, rows, SUB_ROWS_MAX); hdr = find_hdr(rows, n, "音质 / 连接管理"); ci = find_act(rows, n, hdr + 1, "编解码器"); bt_act(&c, ci, rows[ci].act); CHECK(strcmp(g_codec, "SBC") == 0, "编解码器 auto → SBC (实际=%s)", g_codec); printf(g_fail ? "\nRESULT: %d FAIL(S)\n" : "\nRESULT: ALL PASS\n", g_fail); return g_fail; }