feat(bt): 蓝牙P1 — netmgrd BT_CTL权威收口(hcitool con判连接/paired-devices补名/a2dp详情/connect-disconnect-pair-unpair-scan)+launcher蓝牙子页sub_page_t重做(8分组,数据全走netmgrd)+设置键audio.output_bt/bt.codec;修act冲突+sub悬垂;宿主82PASS+bt_ctl/bt_page全过

This commit is contained in:
AskaEth committed 2026-09-03 20:01:13 +08:00
1 parent 8c677f127f
commit eb07415f7b
8 files changed
+1178 -90

No files matched your search

+225 -59
View File
@@ -1,46 +1,98 @@
/* bt_page.c — MeowOS 系统设置「蓝牙独立子页面」(安卓风,M2.76)。
/* bt_page.c — MeowOS 系统设置「蓝牙独立子页面」(安卓风,M2.76;P1 权威收口重做)。
*
* 页面(sub_page_t 脚手架 subpage.c 驱动):
* - 顶栏:返回 + 标题「蓝牙」 + 右上开关(network.bluetooth_enabled)
* - 已连接设备:tap → bluetoothctl disconnect(角色经 bt_role_refresh 判定)
* - 已配对设备:tap → bluetoothctl connect(已连者标「已连接」)
* - 附近设备:扫描发现(net_bt_scan,后台有界 bluetoothctl 写 /tmp 缓存,异步不阻塞)——
* 未配对者 tap → pair;已配对者 tap → connect。实机配对成功率/超时需真机验证。
* 【P1 背景】BLUETOOTH_FULL_OVERHAUL_PLAN §六/§八:连接/断开/配对/取消配对/扫描/A2DP 状态
* 查询全部收口进 netmgrd(BT_CTL 消息扩展),本子页**只做客户端**——经 netmgrd socket/IPC 查询
* 与下发,不再 bluetoothctl popen 直连(直连仅作 netmgrd 不可达时的回退保留)。
*
* 与网络线共用 net.c(net_bt_connected/paired/scan/scan_trigger/hci_powered/bt_role)。
* 开关写 network.bluetooth_enabled + hciconfig;连接/断开/配对走 bluetoothctl(带超时)。
* 【已连接设备显示不全根因修复】旧实现用 `bluetoothctl devices Connected`(慢 + 只反映 agent
* 缓存视图,popen 1500ms 易截断)→ 已连接列表不全。P1 改权威 = netmgrd 读**内核 ACL**
* (hcitool con,快速/权威/不受 bluetoothctl 过滤影响),名经 paired/devices 缓存兜底,A2DP 详情
* 按 MAC join(codec/采样率/acquired/role)。见 netmgrd `bt connected`/`bt a2dp`。
*
* 【页面结构(sub_page_t,plan §六)】顶栏「蓝牙」+ 右上开关,分组列表:
* ① 蓝牙开关(顶栏)② A2DP 状态摘要(设备名·codec·采样率·输出=蓝牙/DAC)③ 输出目标
* (蓝牙/本地,切下发 audio.output_bt,P2 接 audiod)④ 已连接设备(tap 断开/详情)⑤ 已配对
* 设备(tap 连接/断开)⑥ 附近设备(扫描节流触发)⑦ 音质/连接管理(codec 选择、P2 参数说明)
* ⑧ 功耗档(扫描节流/发现超时/后台保持说明)。
*
* 【扫描节流(§七)】进页 nm_bt_scan_on、退页/超时 nm_bt_scan_off;7s 节流由 netmgrd 统一控。
*
* 与网络线共用 net.c(net_bt_* 直连回退 + bt_role 角色判定)。开关写 network.bluetooth_enabled +
* netmgrd on/off;连接/断开/配对经 netmgrd(netmgr_ipc.c nm_bt_* 封装)。
*/
#include "meow_launcher_internal.h"
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#define BT_ACT_CONN_BASE 2000 /* 已连接 idx(tap→disconnect) */
#define BT_ACT_PAIRED_BASE 3000 /* 已配对 idx(tap→connect) */
#define BT_ACT_NEAR_BASE 4000 /* 附近 idx(tap→pair/connect) */
#define BT_ACT_CONN_BASE 2000 /* 已连接 idx(tap→断开) */
#define BT_ACT_PAIRED_BASE 3000 /* 已配对 idx(tap→连接/断开) */
#define BT_ACT_NEAR_BASE 4000 /* 附近 idx(tap→配对/连接) */
#define BT_ACT_OUTPUT 5000 /* 输出目标(tap→蓝牙/本地切换) */
#define BT_ACT_CODEC 5001 /* 编解码器(tap→循环选择) */
#define BT_CODEC_AUTO 0
#define BT_CODEC_N 5 /* auto/SBC/AAC/aptX/LDAC */
static const char *bt_codec_names[BT_CODEC_N] = { "auto", "SBC", "AAC", "aptX", "LDAC" };
typedef struct {
meow_net_t conn[S_NET_MAX];
meow_net_t paired[S_NET_MAX];
meow_net_t near[S_NET_MAX];
int conn_n, paired_n, near_n;
bt_ep_t a2dp[S_NET_MAX];
int conn_n, paired_n, near_n, a2dp_n;
int bt_on;
int full; /* 1=本次 rows() 全量重扫(conn/paired popen);0=仅附近(file)+角色(节流) */
int output_bt; /* 0=DAC/本地,1=蓝牙 */
int full; /* 1=本次 rows() 全量重扫(conn/paired/a2dp);0=仅附近(缓存)+角色(节流) */
char mode[48]; /* bt_role 中文描述(次行摘要) */
/* 行副文本池:sub_page_t 行 label/sub 为 const char*,跨 rows() 返回后仍须有效(直到下次
* rows() 重填)。本地栈缓冲会悬垂(agent-396 根因同类),故格式化 sub 一律写进本池。 */
char pool[512];
int pool_off;
} bt_ctx_t;
/* 设备 MAC(net_bt_* 已填 mac) */
/* 格式化并拷入 ctx 行副文本池(返回稳定指针;溢出则绕回——512B 远超单页行数所需)。 */
static char *bt_pool(bt_ctx_t *c, const char *fmt, ...)
{
va_list ap; char tmp[128];
va_start(ap, fmt); vsnprintf(tmp, sizeof tmp, fmt, ap); va_end(ap);
int l = (int)strlen(tmp);
if (c->pool_off + l + 1 > (int)sizeof c->pool) c->pool_off = 0;
char *p = &c->pool[c->pool_off];
memcpy(p, tmp, (size_t)l + 1);
c->pool_off += l + 1;
return p;
}
/* ---- 设备 MAC 相关 ---- */
static int bt_is_conn(const bt_ctx_t *c, const char *mac)
{
if (!mac[0]) return 0;
for (int i = 0; i < c->conn_n; i++) if (!strcmp(c->conn[i].mac, mac)) return 1;
return 0;
}
static int bt_is_paired(const bt_ctx_t *c, const char *mac)
{
if (!mac[0]) return 0;
for (int i = 0; i < c->paired_n; i++) if (!strcmp(c->paired[i].mac, mac)) return 1;
return 0;
}
/* 在 a2dp 端点表找 MAC(返回 idx;-1=无) */
static int bt_ep_idx(const bt_ctx_t *c, const char *mac)
{
if (!mac[0]) return -1;
for (int i = 0; i < c->a2dp_n; i++) if (!strcmp(c->a2dp[i].mac, mac)) return i;
return -1;
}
/* 从 conn 表找设备名(返回指针到 c->conn[i].name,未命中返回 mac) */
static const char *bt_conn_name(const bt_ctx_t *c, const char *mac)
{
if (mac[0]) for (int i = 0; i < c->conn_n; i++) if (!strcmp(c->conn[i].mac, mac)) return c->conn[i].name;
return mac;
}
/* 顶栏开关当前值 */
@@ -53,7 +105,7 @@ static int bt_top_sw(void *ctx, int *on)
return 1;
}
/* 顶栏开关切换:写设置 + on/off(netmgr IPC,回退直连 hciconfig)+ 触发扫描 + 同步状态栏 */
/* 顶栏开关切换:写设置 + on/off(netmgrd IPC,回退直连 hciconfig)+ 触发扫描 + 同步状态栏 */
static void bt_top_sw_set(void *ctx, int on)
{
(void)ctx;
@@ -63,48 +115,91 @@ static void bt_top_sw_set(void *ctx, int on)
bt_role_refresh(1);
paint_sb_right();
meow_settings_save();
if (on) net_bt_scan_trigger();
if (on) nm_bt_scan_on();
}
/* 填行:刷新数据后构建分组列表。
* conn/paired 走 bluetoothctl(慢);仅全量(full)时重扫,避开子页 live tick 反复 spawn。 */
/* 填行:刷新数据后构建分组列表(权威=netmgrd;仅全量(full)时重扫 conn/paired/a2dp,
* 避开子页 live tick 反复 IPC)。 */
static int bt_rows(void *ctx, sub_row_t *rbuf, int max)
{
bt_ctx_t *c = ctx;
int full = c->full; c->full = 0; /* 消费一次全量标记 */
c->pool_off = 0; /* 行副文本池绕回(上轮行字符串已被重填替换) */
c->bt_on = nm_bt_powered(); /* IPC status;回退直连 hci_powered() */
if (full || c->conn_n < 0) { /* 首次/动作后:全量重扫 conn/paired */
/* conn 列表 netmgrd 无子命令(BT_CTL=status/on/off/paired),保持直连 bluetoothctl;
* paired 走 IPC(权威),回退直连。 */
c->conn_n = net_bt_connected(c->conn, S_NET_MAX);
c->output_bt = 0;
if (meow_settings_get_bool("audio.output_bt", &c->output_bt) != 0) c->output_bt = 0;
if (full || c->conn_n < 0) { /* 首次/动作后:全量重扫 conn/paired/a2dp(全权威) */
c->conn_n = nm_bt_connected(c->conn, S_NET_MAX);
c->paired_n = nm_bt_paired(c->paired, S_NET_MAX);
c->a2dp_n = nm_bt_a2dp(c->a2dp, S_NET_MAX);
if (c->conn_n < 0) c->conn_n = 0;
if (c->paired_n < 0) c->paired_n = 0;
if (c->a2dp_n < 0) c->a2dp_n = 0;
}
c->near_n = net_bt_scan(c->near, S_NET_MAX); /* 附近:读 /tmp 缓存(快,实测无 popen) */
c->near_n = nm_bt_devices(c->near, S_NET_MAX); /* 附近:扫描缓存(netmgrd 读 /tmp,快) */
if (c->near_n < 0) c->near_n = 0;
/* 角色描述:节流(≤5s)重探,读缓存,避免子页驻留期间反复 popen */
net_bt_mode_str(c->mode, sizeof c->mode);
int n = 0;
/* 降级占位:netmgrd 不可达 → 提示(直连仍在跑,保功能) */
/* 降级占位:netmgrd 不可达 → 提示(直连回退仍在跑,保功能) */
if (!netmgr_ok() && n < max) {
rbuf[n].kind = SUB_HDR; rbuf[n].label = "网络管理服务未运行(直连模式)";
rbuf[n].sub = NULL; rbuf[n].on = 0; rbuf[n].act = 0; n++;
}
/* 已连接设备 */
/* ② A2DP 状态摘要(设备名·codec·采样率·输出=蓝牙/DAC) */
{
const bt_ep_t *ep = NULL;
for (int i = 0; i < c->a2dp_n; i++) if (c->a2dp[i].acquired) { ep = &c->a2dp[i]; break; }
if (!ep && c->a2dp_n > 0) ep = &c->a2dp[0];
const char *sub;
if (c->conn_n > 0 && n < max) {
const char *nm = bt_conn_name(c, ep ? ep->mac : c->conn[0].mac);
if (ep && (ep->codec[0] || ep->rate[0]))
sub = bt_pool(c, "%s%s%s · 输出=%s",
ep->codec[0] ? ep->codec : "?", ep->codec[0] && ep->rate[0] ? " " : "",
ep->rate[0] ? ep->rate : "", c->output_bt ? "蓝牙" : "DAC");
else
sub = bt_pool(c, "%s · 输出=%s", c->mode, c->output_bt ? "蓝牙" : "DAC");
rbuf[n].kind = SUB_ROW; rbuf[n].label = nm ? nm : c->conn[0].mac;
rbuf[n].sub = sub; rbuf[n].on = 0; rbuf[n].act = -1; n++;
} else if (n < max) {
sub = bt_pool(c, "%s · 输出=%s", c->mode, c->output_bt ? "蓝牙" : "DAC");
rbuf[n].kind = SUB_ROW; rbuf[n].label = "未连接蓝牙设备";
rbuf[n].sub = sub; rbuf[n].on = 0; rbuf[n].act = -1; n++;
}
}
/* ③ 输出目标(蓝牙/本地) */
if (n < max) { rbuf[n].kind = SUB_HDR; rbuf[n].label = "输出目标"; rbuf[n].sub = NULL; rbuf[n].on = 0; rbuf[n].act = 0; n++; }
if (n < max) {
rbuf[n].kind = SUB_ACT; rbuf[n].label = "输出目标";
rbuf[n].sub = c->output_bt ? "蓝牙(耳机/远端)" : "本地(DAC)";
rbuf[n].on = c->output_bt; rbuf[n].act = BT_ACT_OUTPUT; n++;
}
/* ④ 已连接设备 */
if (n < max) { rbuf[n].kind = SUB_HDR; rbuf[n].label = "已连接设备"; rbuf[n].sub = NULL; rbuf[n].on = 0; rbuf[n].act = 0; n++; }
if (c->conn_n == 0) {
if (n < max) { rbuf[n].kind = SUB_ROW; rbuf[n].label = "无已连接设备"; rbuf[n].sub = ""; rbuf[n].on = 0; rbuf[n].act = -2; n++; }
} else for (int i = 0; i < c->conn_n && n < max; i++) {
const bt_ep_t *ep = bt_ep_idx(c, c->conn[i].mac) >= 0 ? &c->a2dp[bt_ep_idx(c, c->conn[i].mac)] : NULL;
const char *sub;
if (ep && ep->codec[0])
sub = bt_pool(c, "%s%s%s%s",
ep->codec[0] ? ep->codec : "", ep->rate[0] ? " " : "", ep->rate[0] ? "· " : "",
ep->acquired ? "播放中" : "");
else sub = c->conn[i].mac; /* mac 拷在 ctx(稳定) */
rbuf[n].kind = SUB_ACT;
rbuf[n].label = c->conn[i].name[0] ? c->conn[i].name : c->conn[i].mac;
rbuf[n].sub = c->conn[i].mac;
rbuf[n].sub = sub[0] ? sub : c->conn[i].mac;
rbuf[n].on = 1; rbuf[n].act = BT_ACT_CONN_BASE + i;
n++;
}
/* 已配对设备 */
/* ⑤ 已配对设备 */
if (n < max) { rbuf[n].kind = SUB_HDR; rbuf[n].label = "已配对设备"; rbuf[n].sub = NULL; rbuf[n].on = 0; rbuf[n].act = 0; n++; }
if (c->paired_n == 0) {
if (n < max) { rbuf[n].kind = SUB_ROW; rbuf[n].label = "无已配对设备"; rbuf[n].sub = ""; rbuf[n].on = 0; rbuf[n].act = -2; n++; }
@@ -117,7 +212,8 @@ static int bt_rows(void *ctx, sub_row_t *rbuf, int max)
rbuf[n].act = BT_ACT_PAIRED_BASE + i;
n++;
}
/* 附近设备(扫描发现,未配对) */
/* ⑥ 附近设备(扫描发现,未配对;netmgrd 权威扫描缓存) */
if (n < max) { rbuf[n].kind = SUB_HDR; rbuf[n].label = "附近设备"; rbuf[n].sub = NULL; rbuf[n].on = 0; rbuf[n].act = 0; n++; }
if (c->near_n == 0) {
if (n < max) { rbuf[n].kind = SUB_ROW; rbuf[n].label = "附近无设备(点顶部开关后自动扫描)"; rbuf[n].sub = ""; rbuf[n].on = 0; rbuf[n].act = -2; n++; }
@@ -130,30 +226,66 @@ static int bt_rows(void *ctx, sub_row_t *rbuf, int max)
rbuf[n].on = 0; rbuf[n].act = BT_ACT_NEAR_BASE + i;
n++;
}
/* ⑦ 音质/连接管理 */
if (n < max) { rbuf[n].kind = SUB_HDR; rbuf[n].label = "音质 / 连接管理"; rbuf[n].sub = NULL; rbuf[n].on = 0; rbuf[n].act = 0; n++; }
{
int idx = BT_CODEC_AUTO;
char codec[16] = "auto";
if (meow_settings_get_str("bt.codec", codec, sizeof codec) == 0)
for (int k = 0; k < BT_CODEC_N; k++) if (!strcasecmp(codec, bt_codec_names[k])) { idx = k; break; }
rbuf[n].kind = SUB_ACT; rbuf[n].label = "编解码器";
rbuf[n].sub = bt_pool(c, "%s%s", bt_codec_names[idx], idx ? "(偏好,P2 生效)" : "");
rbuf[n].on = 0;
rbuf[n].act = BT_ACT_CODEC; n++;
}
if (n < max) { rbuf[n].kind = SUB_ROW; rbuf[n].label = "SBC 质量"; rbuf[n].sub = "P2:bluealsa --sbc-quality 启动参数"; rbuf[n].on = 0; rbuf[n].act = -2; n++; }
if (n < max) { rbuf[n].kind = SUB_ROW; rbuf[n].label = "强制 44.1k"; rbuf[n].sub = "P2:--a2dp-force-audio-cd"; rbuf[n].on = 0; rbuf[n].act = -2; n++; }
if (n < max) { rbuf[n].kind = SUB_ROW; rbuf[n].label = "音量控制"; rbuf[n].sub = "蓝牙音量由耳机/远端(AVRCP)控制"; rbuf[n].on = 0; rbuf[n].act = -2; n++; }
/* ⑧ 功耗档 */
if (n < max) { rbuf[n].kind = SUB_HDR; rbuf[n].label = "功耗"; rbuf[n].sub = NULL; rbuf[n].on = 0; rbuf[n].act = 0; n++; }
if (n < max) { rbuf[n].kind = SUB_ROW; rbuf[n].label = "扫描节流"; rbuf[n].sub = "进页自动扫描,退页/7s 停止"; rbuf[n].on = 0; rbuf[n].act = -2; n++; }
if (n < max) { rbuf[n].kind = SUB_ROW; rbuf[n].label = "发现超时"; rbuf[n].sub = "source 无需可发现(有限超时)"; rbuf[n].on = 0; rbuf[n].act = -2; n++; }
if (n < max) { rbuf[n].kind = SUB_ROW; rbuf[n].label = "后台保持"; rbuf[n].sub = "未播放时无流零开销"; rbuf[n].on = 0; rbuf[n].act = -2; n++; }
return n;
}
/* 执行 bluetoothctl 命令(带超时)+ 结果弹窗反馈 */
static void bt_run(const char *title, const char *cmd)
/* 动作反馈(netmgrd 结果 / 直连回退结果)弹窗反馈 */
static void bt_feedback(const char *title, int rc)
{
char *o = popen_read_tmo(8000, cmd);
char line[48];
const char *txt[4]; txt[0] = line;
int ok = o && (strstr(o, "successful") || strstr(o, "Connected") ||
strstr(o, "already") || strstr(o, "Paired"));
snprintf(line, sizeof line, "%s", ok ? "操作成功" : "操作失败/超时");
if (o) free(o);
const char *txt[S_NET_MAX]; txt[0] = line;
snprintf(line, sizeof line, "%s", rc == MEOW_OK ? "操作成功" : "操作失败/超时");
s_dd_modal(title, txt, 1, -1);
}
/* 动作行(SUB_ACT)点击:连接/断开/配对 */
/* 动作行(SUB_ACT)点击:连接/断开/配对/取消配对/输出目标/编解码器 */
static int bt_act(void *ctx, int idx, int act)
{
(void)idx;
bt_ctx_t *c = ctx;
char mac[24] = "";
/* 输出目标/编解码器:与本页设备行 act 段(CONN/PAIRED/NEAR base)不冲突,先处理 */
if (act == BT_ACT_OUTPUT) {
c->output_bt = !c->output_bt;
meow_settings_set_bool("audio.output_bt", c->output_bt);
meow_settings_save();
logmsg("bt_page: output_target=%s", c->output_bt ? "bt" : "dac");
return 1; /* 重绘 + 重填行 */
}
if (act == BT_ACT_CODEC) {
char codec[16] = "auto"; int idx0 = BT_CODEC_AUTO;
if (meow_settings_get_str("bt.codec", codec, sizeof codec) == 0)
for (int k = 0; k < BT_CODEC_N; k++) if (!strcasecmp(codec, bt_codec_names[k])) { idx0 = k; break; }
int ni = (idx0 + 1) % BT_CODEC_N;
meow_settings_set_str("bt.codec", bt_codec_names[ni]);
meow_settings_save();
logmsg("bt_page: codec=%s", bt_codec_names[ni]);
return 1;
}
/* 定位 MAC(按 act 段) */
if (act >= BT_ACT_NEAR_BASE) {
int i = act - BT_ACT_NEAR_BASE;
@@ -168,43 +300,75 @@ static int bt_act(void *ctx, int idx, int act)
if (i < 0 || i >= c->conn_n) return 0;
snprintf(mac, sizeof mac, "%s", c->conn[i].mac);
}
if (!mac[0]) return 0;
if (act >= BT_ACT_NEAR_BASE) {
/* 附近未配对 → pair;已配对(理论上过滤了)→ connect */
char cmd[192];
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin bluetoothctl pair %s 2>/dev/null", mac);
bt_run("配对", cmd);
/* 附近:未配对 → pair;已配对(理论上过滤了)→ connect */
bt_feedback("配对", nm_bt_pair(mac));
} else if (act >= BT_ACT_PAIRED_BASE) {
/* 已配对:未连 → connect;已连 → disconnect */
if (bt_is_conn(c, mac)) {
char cmd[192];
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin bluetoothctl disconnect %s 2>/dev/null", mac);
bt_run("断开", cmd);
} else {
char cmd[192];
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin bluetoothctl connect %s 2>/dev/null", mac);
bt_run("连接", cmd);
}
if (bt_is_conn(c, mac)) bt_feedback("断开", nm_bt_disconnect(mac));
else bt_feedback("连接", nm_bt_connect(mac));
} else {
/* 已连接 → disconnect */
char cmd[192];
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin bluetoothctl disconnect %s 2>/dev/null", mac);
bt_run("断开", cmd);
bt_feedback("断开", nm_bt_disconnect(mac));
}
bt_role_refresh(1);
net_bt_scan_trigger();
nm_bt_scan_on();
c->full = 1; /* 动作后:下一次 rows() 全量重扫 */
logmsg("bt_page: act act=%d mac=%s", act, mac);
return 1; /* 重绘 + 重填行 */
}
/* 点普通行(A2DP 摘要等):弹 A2DP/设备详情。仅 A2DP 摘要(act=-1)响应;其余 info 行(act=-2)无操作。 */
static int bt_detail(void *ctx, int idx, int act)
{
(void)idx;
if (act != -1) return 0;
bt_ctx_t *c = ctx;
if (c->a2dp_n > 0) {
const bt_ep_t *ep = &c->a2dp[0];
char buf[S_NET_MAX][48];
const char *item[S_NET_MAX];
int n = 0;
for (int i = 0; i < c->a2dp_n; i++) if (c->a2dp[i].acquired) { ep = &c->a2dp[i]; break; }
snprintf(buf[n], 48, "设备名: %s", bt_conn_name(c, ep->mac)); item[n] = buf[n]; n++;
snprintf(buf[n], 48, "MAC: %s", ep->mac[0] ? ep->mac : "-"); item[n] = buf[n]; n++;
snprintf(buf[n], 48, "codec: %s", ep->codec[0] ? ep->codec : "-"); item[n] = buf[n]; n++;
snprintf(buf[n], 48, "采样率: %s", ep->rate[0] ? ep->rate : "-"); item[n] = buf[n]; n++;
snprintf(buf[n], 48, "声道/位深: %s/%s", ep->chn[0] ? ep->chn : "-", ep->bits[0] ? ep->bits : "-");
item[n] = buf[n]; n++;
snprintf(buf[n], 48, "传输: %s", ep->acquired ? "播放中" : "空闲"); item[n] = buf[n]; n++;
snprintf(buf[n], 48, "角色: %s", !strcmp(ep->role, "src") ? "设备=source(送流)" :
!strcmp(ep->role, "snk") ? "设备=sink(被连)" : "未知");
item[n] = buf[n]; n++;
s_dd_modal("A2DP 详情", item, n, -1);
return 1;
}
if (c->conn_n > 0) {
char buf[S_NET_MAX][48];
const char *item[S_NET_MAX];
int n = 0;
snprintf(buf[n], 48, "设备名: %s", c->conn[0].name[0] ? c->conn[0].name : c->conn[0].mac);
item[n] = buf[n]; n++;
snprintf(buf[n], 48, "MAC: %s", c->conn[0].mac); item[n] = buf[n]; n++;
snprintf(buf[n], 48, "状态: 已连接"); item[n] = buf[n]; n++;
snprintf(buf[n], 48, "输出: %s", c->output_bt ? "蓝牙" : "DAC"); item[n] = buf[n]; n++;
s_dd_modal("蓝牙详情", item, n, -1);
return 1;
}
return 0;
}
/* 运行蓝牙独立子页面(阻塞到返回) */
int run_bt_page(void)
{
static bt_ctx_t c;
memset(&c, 0, sizeof c);
c.conn_n = -1;
sub_page_t p;
memset(&p, 0, sizeof p);
p.title = "蓝牙";
@@ -212,11 +376,13 @@ int run_bt_page(void)
p.top_sw_set = bt_top_sw_set;
p.rows = bt_rows;
p.act = bt_act;
p.detail = NULL; /* 蓝牙动作都走 SUB_ACT(act),无 SUB_ROW 详情 */
p.detail = bt_detail; /* SUB_ROW(A2DP 摘要等)→ A2DP 详情弹窗 */
p.live = 1; /* 附近扫描驻留更新(约 2s 重扫行;仅附近/角色,不反复 spawn) */
p.ctx = &c;
c.full = 1; /* 首行全量重扫 */
net_bt_scan_trigger(); /* 进页立即触发一次附近扫描(异步,不阻塞) */
nm_bt_scan_on(); /* 进页:触发一次附近扫描(节流;不进蓝牙不空转 §七) */
bt_role_refresh(1);
return meow_subpage_run(&p);
int rc = meow_subpage_run(&p);
nm_bt_scan_off(); /* 退页:扫描收尾(§七 省电) */
return rc;
}