Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/bt_page.c
T

389 lines
19 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* bt_page.c — MeowOS 系统设置「蓝牙独立子页面」(安卓风,M2.76;P1 权威收口重做)。
*
* 【P1 背景】BLUETOOTH_FULL_OVERHAUL_PLAN §六/§八:连接/断开/配对/取消配对/扫描/A2DP 状态
* 查询全部收口进 netmgrd(BT_CTL 消息扩展),本子页**只做客户端**——经 netmgrd socket/IPC 查询
* 与下发,不再 bluetoothctl popen 直连(直连仅作 netmgrd 不可达时的回退保留)。
*
* 【已连接设备显示不全根因修复】旧实现用 `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→断开) */
#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];
bt_ep_t a2dp[S_NET_MAX];
int conn_n, paired_n, near_n, a2dp_n;
int bt_on;
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;
/* 格式化并拷入 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;
}
/* 顶栏开关当前值 */
static int bt_top_sw(void *ctx, int *on)
{
(void)ctx;
int v = 0;
if (meow_settings_get_bool("network.bluetooth_enabled", &v) != 0) v = 0;
*on = v;
return 1;
}
/* 顶栏开关切换:写设置 + on/off(netmgrd IPC,回退直连 hciconfig)+ 触发扫描 + 同步状态栏 */
static void bt_top_sw_set(void *ctx, int on)
{
(void)ctx;
meow_settings_set_bool("network.bluetooth_enabled", on);
nm_bt_onoff(on);
g_sys.bt_on = on;
bt_role_refresh(1);
paint_sb_right();
meow_settings_save();
if (on) nm_bt_scan_on();
}
/* 填行:刷新数据后构建分组列表(权威=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() */
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 = 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 不可达 → 提示(直连回退仍在跑,保功能) */
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 = 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++; }
} else for (int i = 0; i < c->paired_n && n < max; i++) {
rbuf[n].kind = SUB_ACT;
rbuf[n].label = c->paired[i].name[0] ? c->paired[i].name : c->paired[i].mac;
rbuf[n].sub = bt_is_conn(c, c->paired[i].mac) ? "已连接"
: (c->paired[i].mac[0] ? c->paired[i].mac : "");
rbuf[n].on = bt_is_conn(c, c->paired[i].mac);
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++; }
} else for (int i = 0; i < c->near_n && n < max; i++) {
int is_paired = bt_is_paired(c, c->near[i].mac);
if (is_paired) continue; /* 已配对者归于「已配对设备」,附近只列未配对 */
rbuf[n].kind = SUB_ACT;
rbuf[n].label = c->near[i].name[0] ? c->near[i].name : c->near[i].mac;
rbuf[n].sub = c->near[i].mac;
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;
}
/* 动作反馈(netmgrd 结果 / 直连回退结果)弹窗反馈 */
static void bt_feedback(const char *title, int rc)
{
char line[48];
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)点击:连接/断开/配对/取消配对/输出目标/编解码器 */
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;
if (i < 0 || i >= c->near_n) return 0;
snprintf(mac, sizeof mac, "%s", c->near[i].mac);
} else if (act >= BT_ACT_PAIRED_BASE) {
int i = act - BT_ACT_PAIRED_BASE;
if (i < 0 || i >= c->paired_n) return 0;
snprintf(mac, sizeof mac, "%s", c->paired[i].mac);
} else if (act >= BT_ACT_CONN_BASE) {
int i = act - BT_ACT_CONN_BASE;
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 */
bt_feedback("配对", nm_bt_pair(mac));
} else if (act >= BT_ACT_PAIRED_BASE) {
/* 已配对:未连 → connect;已连 → disconnect */
if (bt_is_conn(c, mac)) bt_feedback("断开", nm_bt_disconnect(mac));
else bt_feedback("连接", nm_bt_connect(mac));
} else {
/* 已连接 → disconnect */
bt_feedback("断开", nm_bt_disconnect(mac));
}
bt_role_refresh(1);
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 = "蓝牙";
p.top_sw = bt_top_sw;
p.top_sw_set = bt_top_sw_set;
p.rows = bt_rows;
p.act = bt_act;
p.detail = bt_detail; /* SUB_ROW(A2DP 摘要等)→ A2DP 详情弹窗 */
p.live = 1; /* 附近扫描驻留更新(约 2s 重扫行;仅附近/角色,不反复 spawn) */
p.ctx = &c;
c.full = 1; /* 首行全量重扫 */
nm_bt_scan_on(); /* 进页:触发一次附近扫描(节流;不进蓝牙不空转 §七) */
bt_role_refresh(1);
int rc = meow_subpage_run(&p);
nm_bt_scan_off(); /* 退页:扫描收尾(§七 省电) */
return rc;
}