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:
1 parent
8c677f127f
commit
eb07415f7b
8 files changed
+1178
-90
No files matched your search
@@ -69,6 +69,9 @@ static const char *g_sock = "/usr/data/meow_net.sock"; /* MEOW_NET_S
|
||||
static const char *g_wpa_cli = "wpa_cli"; /* MEOW_WPA_CLI */
|
||||
static const char *g_hciconfig = "hciconfig"; /* MEOW_HCICONFIG */
|
||||
static const char *g_bluetoothctl= "bluetoothctl"; /* MEOW_BLUETOOTHCTL */
|
||||
static const char *g_hcitool = "hcitool"; /* MEOW_HCITOOL(内核 ACL 连接权威) */
|
||||
static const char *g_bluealsa_cli= "bluealsa-cli"; /* MEOW_BLUEALSA_CLI(A2DP 传输详情) */
|
||||
static const char *g_bt_scan_cache = "/tmp/meow_bt_scan.txt"; /* MEOW_BT_SCAN_CACHE(扫描缓存;launcher net.c 同路径) */
|
||||
static const char *g_ip = "ip"; /* MEOW_IP */
|
||||
static const char *g_wifi_if = "wlan0"; /* MEOW_WIFI_IF */
|
||||
|
||||
@@ -353,6 +356,7 @@ typedef struct {
|
||||
int powered;
|
||||
char address[32];
|
||||
int paired_n; /* -1=null */
|
||||
int connected_n; /* 已连接(内核 ACL 权威)计数;-1=null */
|
||||
} bt_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -603,10 +607,12 @@ static void wifi_status(wifi_t *w)
|
||||
}
|
||||
|
||||
/* ---------------- 蓝牙:hciconfig/bluetoothctl 包装 ---------------- */
|
||||
static int bt_acl_macs(char macs[][24], int max); /* 前向声明(bt_status 早于其定义) */
|
||||
static void bt_status(bt_t *b)
|
||||
{
|
||||
memset(b, 0, sizeof *b);
|
||||
b->paired_n = -1;
|
||||
b->connected_n = -1;
|
||||
char p[192];
|
||||
snprintf(p, sizeof p, "%s/hci0", g_bt_dir);
|
||||
if (access(p, F_OK) == 0) b->available = 1;
|
||||
@@ -670,6 +676,286 @@ static void bt_status(bt_t *b)
|
||||
}
|
||||
b->paired_n = n;
|
||||
}
|
||||
/* 已连接数:内核 ACL(hcitool con)权威;快速,不受 bluetoothctl 过滤/慢启动影响。 */
|
||||
{
|
||||
char macs[8][24];
|
||||
b->connected_n = bt_acl_macs(macs, 8);
|
||||
}
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* P1:蓝牙权威收口(BLUETOOTH_FULL_OVERHAUL_PLAN §六/§八 P1)。
|
||||
* 权威路径设计(修复「已连接设备显示不全」根因):
|
||||
* - 已连接 = 内核 ACL(hcitool con,快速/权威/不受 bluetoothctl 过滤影响),
|
||||
* 名/配对来自 bluetoothctl `paired-devices`/`devices`(蓝虫子缓存)——不再依赖
|
||||
* bluetoothctl `devices Connected`(慢 + 只反映 agent 缓存视图,易截断)。
|
||||
* - A2DP 详情 = bluealsa-cli a2dp list(codec/采样率/声道/位深/传输状态/角色)。
|
||||
* - 扫描 = netmgrd 统一生命周期(scan on 才扫、off/超时收尾;缓存 /tmp/meow_bt_scan.txt)。
|
||||
* 输出均为文本(m_net_cmd_result_t.text),launcher 只做客户端解析,不再 bluetoothctl popen。
|
||||
* ================================================================ */
|
||||
static int bt_hex(int c)
|
||||
{
|
||||
if (c >= '0' && c <= '9') return c - '0';
|
||||
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||
return -1;
|
||||
}
|
||||
static int bt_mac_eq(const char *a, const char *b)
|
||||
{
|
||||
for (int i = 0; i < 17; i++) {
|
||||
if (bt_hex((unsigned char)a[i]) != bt_hex((unsigned char)b[i])) return 0;
|
||||
if (i % 3 == 2 && a[i] != b[i]) return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
/* 在行内找第一个形如 XX:XX:XX:XX:XX:XX 的 MAC,返其指针(否则 NULL)。 */
|
||||
static const char *bt_find_mac(const char *s)
|
||||
{
|
||||
while (*s) {
|
||||
int ok = 1;
|
||||
for (int i = 0; i < 17; i++) {
|
||||
int exp_colon = (i % 3 == 2);
|
||||
if (exp_colon) { if (s[i] != ':') { ok = 0; break; } }
|
||||
else if (bt_hex((unsigned char)s[i]) < 0) { ok = 0; break; }
|
||||
}
|
||||
if (ok) return s;
|
||||
s++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* 内核 ACL 连接 MAC(hcitool con)。返回数(≤max)。 */
|
||||
static int bt_acl_macs(char macs[][24], int max)
|
||||
{
|
||||
char cmd[160];
|
||||
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s con 2>/dev/null", g_hcitool);
|
||||
char buf[1024];
|
||||
if (run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd) <= 0) return 0;
|
||||
int n = 0;
|
||||
char *line = buf;
|
||||
while (line && *line && n < max) {
|
||||
char *nl = strchr(line, '\n'); if (nl) *nl = 0;
|
||||
const char *m = bt_find_mac(line);
|
||||
if (m) { memcpy(macs[n], m, 17); macs[n][17] = 0; n++; }
|
||||
line = nl ? nl + 1 : NULL;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/* 设备名解析(bluetoothctl `paired-devices`)+`devices` 两次兜底;命中返 1。 */
|
||||
static int bt_name_for_mac(const char *mac, char *name, int nlen)
|
||||
{
|
||||
if (name && nlen > 0) name[0] = 0;
|
||||
static const char *subs[] = { "paired-devices", "devices" };
|
||||
for (int s = 0; s < 2; s++) {
|
||||
char cmd[176];
|
||||
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s %s 2>/dev/null",
|
||||
g_bluetoothctl, subs[s]);
|
||||
char buf[1024];
|
||||
if (run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd) <= 0) continue;
|
||||
char *line = buf;
|
||||
while (line && *line) {
|
||||
char *nl = strchr(line, '\n'); if (nl) *nl = 0;
|
||||
char *q = line; while (*q == ' ' || *q == '\t') q++;
|
||||
if (!strncmp(q, "Device ", 7)) {
|
||||
q += 7;
|
||||
char *m = q; while (*m && *m != ' ' && *m != '\t' && *m != '\n' && *m != '\r') m++;
|
||||
int ml = (int)(m - q);
|
||||
if (ml == 17 && bt_mac_eq(q, mac)) {
|
||||
char *nm = m; while (*nm == ' ' || *nm == '\t') nm++;
|
||||
char *e = nm; while (*e && *e != '\n' && *e != '\r') e++;
|
||||
*e = 0;
|
||||
int l = (int)(e - nm); if (l > nlen - 1) l = nlen - 1;
|
||||
if (l > 0) memcpy(name, nm, (size_t)l), name[l] = 0;
|
||||
else { memcpy(name, mac, 17); name[17] = 0; }
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
line = nl ? nl + 1 : NULL;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 已连接设备文本(权威=内核 ACL;名经 paired/devices 兜底)。行格式 "Device <MAC> <Name>"。 */
|
||||
static void bt_connected_text(char *buf, int cap)
|
||||
{
|
||||
int off = 0;
|
||||
char macs[8][24];
|
||||
int n = bt_acl_macs(macs, 8);
|
||||
for (int i = 0; i < n; i++) {
|
||||
char name[64] = "";
|
||||
bt_name_for_mac(macs[i], name, sizeof name);
|
||||
int l = snprintf(buf + off, (size_t)(cap - off), "Device %s %s\n", macs[i], name);
|
||||
if (l <= 0 || off + l >= cap) break;
|
||||
off += l;
|
||||
}
|
||||
buf[off] = 0;
|
||||
}
|
||||
|
||||
/* A2DP 传输详情:bluealsa-cli a2dp list → 每端点一行
|
||||
* "A2DP <mac> codec=<..> rate=<..> channels=<n> bits=<n> acquired=<0|1> role=<src|snk|->"
|
||||
* 解析宽松:字段缺失留空/0;整串不可辨认时退回原文。 */
|
||||
static void bt_a2dp_text(char *buf, int cap)
|
||||
{
|
||||
char cmd[176];
|
||||
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s a2dp list 2>/dev/null", g_bluealsa_cli);
|
||||
char raw[MEOW_IPC_MAX - 16];
|
||||
if (run_cmd(raw, sizeof raw, RUNCMD_TMO_MS, cmd) <= 0) {
|
||||
snprintf(buf, (size_t)cap, "A2DP none\n");
|
||||
return;
|
||||
}
|
||||
/* 端点累积器 */
|
||||
char emac[24] = "", role[4] = "-";
|
||||
char codec[16] = "", rate[16] = "", chn[8] = "", bits[8] = "";
|
||||
int acquired = -1, has_mac = 0, wrote = 0, off = 0;
|
||||
char *line = raw;
|
||||
while (line && *line) {
|
||||
char *nl = strchr(line, '\n'); if (nl) *nl = 0;
|
||||
char *p = line; while (*p == ' ' || *p == '\t') p++;
|
||||
if (*p == '/') { /* 新端点:/org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/a2dpsrc[:] */
|
||||
if (has_mac && wrote) {
|
||||
int l = snprintf(buf + off, (size_t)(cap - off),
|
||||
"A2DP %s codec=%s rate=%s channels=%s bits=%s acquired=%d role=%s\n",
|
||||
emac, codec[0] ? codec : "-", rate[0] ? rate : "-",
|
||||
chn[0] ? chn : "-", bits[0] ? bits : "-",
|
||||
acquired >= 0 ? acquired : 0, role);
|
||||
if (l > 0 && off + l < cap) off += l;
|
||||
}
|
||||
/* 重置并为新端点解析 MAC/role */
|
||||
emac[0] = role[0] = codec[0] = rate[0] = chn[0] = bits[0] = 0;
|
||||
snprintf(role, sizeof role, "%s", "-");
|
||||
acquired = -1; has_mac = 0; wrote = 0;
|
||||
const char *dv = strstr(p, "dev_");
|
||||
if (dv) {
|
||||
dv += 4;
|
||||
int j = 0;
|
||||
const char *q = dv;
|
||||
while (*q && j < 17) {
|
||||
if (*q == '_') { if (j == 2 || j == 5 || j == 8 || j == 11 || j == 14) emac[j] = ':'; else break; }
|
||||
else if (bt_hex((unsigned char)*q) >= 0) emac[j] = *q;
|
||||
else break;
|
||||
j++; q++;
|
||||
}
|
||||
emac[17] = 0;
|
||||
has_mac = (j == 17);
|
||||
if (strstr(p, "a2dpsrc")) snprintf(role, sizeof role, "%s", "src");
|
||||
else if (strstr(p, "a2dpsnk")) snprintf(role, sizeof role, "%s", "snk");
|
||||
} else has_mac = 0;
|
||||
wrote = 1;
|
||||
line = nl ? nl + 1 : NULL;
|
||||
continue;
|
||||
}
|
||||
/* 字段行:Codec:/Sampling:/Channels:/Bits:/State:/Acquired: */
|
||||
{ const char *v = strstr(p, "Codec:"); if (v && codec[0] == 0) {
|
||||
v += 6; while (*v == ' ' || *v == '\t') v++;
|
||||
{ const char *e = v; while (*e && *e != '\n' && *e != '\r') e++;
|
||||
while (e > v && (e[-1] == ' ' || e[-1] == '\t')) e--;
|
||||
int l = (int)(e - v); if (l > 15) l = 15; memcpy(codec, v, (size_t)l); codec[l] = 0; } } }
|
||||
{ const char *v = strstr(p, "Sampling"); if (v && rate[0] == 0) {
|
||||
v = strchr(v, ':'); if (v) { v++; while (*v == ' ' || *v == '\t') v++;
|
||||
{ const char *e = v; while (*e && *e != '\n' && *e != '\r') e++;
|
||||
while (e > v && (e[-1] == ' ' || e[-1] == '\t')) e--;
|
||||
int l = (int)(e - v); if (l > 15) l = 15; memcpy(rate, v, (size_t)l); rate[l] = 0; } } } }
|
||||
{ const char *v = strstr(p, "Channels:"); if (v && chn[0] == 0) {
|
||||
v += 9; while (*v == ' ' || *v == '\t') v++;
|
||||
{ const char *e = v; while (*e && *e != '\n' && *e != '\r') e++;
|
||||
while (e > v && (e[-1] == ' ' || e[-1] == '\t')) e--;
|
||||
int l = (int)(e - v); if (l > 7) l = 7; memcpy(chn, v, (size_t)l); chn[l] = 0; } } }
|
||||
{ const char *v = strstr(p, "Bits"); if (v && bits[0] == 0) {
|
||||
v = strchr(v, ':'); if (v) { v++; while (*v == ' ' || *v == '\t') v++;
|
||||
{ const char *e = v; while (*e && *e != '\n' && *e != '\r') e++;
|
||||
while (e > v && (e[-1] == ' ' || e[-1] == '\t')) e--;
|
||||
int l = (int)(e - v); if (l > 7) l = 7; memcpy(bits, v, (size_t)l); bits[l] = 0; } } } }
|
||||
if (strstr(p, "State:") || strstr(p, "State :") || strstr(p, "Acquired")) {
|
||||
if (strstr(p, "RUNNING") || strstr(p, "ACTIVE") || strstr(p, "acquired") || strstr(p, "yes"))
|
||||
acquired = 1;
|
||||
}
|
||||
line = nl ? nl + 1 : NULL;
|
||||
}
|
||||
/* 尾端点收尾 */
|
||||
if (has_mac && wrote) {
|
||||
int l = snprintf(buf + off, (size_t)(cap - off),
|
||||
"A2DP %s codec=%s rate=%s channels=%s bits=%s acquired=%d role=%s\n",
|
||||
emac, codec[0] ? codec : "-", rate[0] ? rate : "-",
|
||||
chn[0] ? chn : "-", bits[0] ? bits : "-",
|
||||
acquired >= 0 ? acquired : 0, role);
|
||||
if (l > 0 && off + l < cap) off += l;
|
||||
}
|
||||
if (off == 0) snprintf(buf, (size_t)cap, "A2DP none\n");
|
||||
else buf[off] = 0;
|
||||
}
|
||||
|
||||
/* A2DP 摘要(首个带 codec/rate 的端点)给 overview JSON;失败置空。 */
|
||||
static void bt_a2dp_summary(char *codec, int clen, char *rate, int rlen)
|
||||
{
|
||||
if (codec && clen > 0) codec[0] = 0;
|
||||
if (rate && rlen > 0) rate[0] = 0;
|
||||
char buf[MEOW_IPC_MAX - 16];
|
||||
bt_a2dp_text(buf, sizeof buf);
|
||||
char *line = buf;
|
||||
while (line && *line) {
|
||||
char *nl = strchr(line, '\n'); if (nl) *nl = 0;
|
||||
if (!strncmp(line, "A2DP ", 5)) {
|
||||
const char *c = strstr(line, " codec=");
|
||||
const char *r = strstr(line, " rate=");
|
||||
if (c && codec) { c += 7; const char *e = c; while (*e && *e != ' ' && *e != '\n') e++;
|
||||
int l = (int)(e - c); if (l > clen - 1) l = clen - 1; memcpy(codec, c, (size_t)l); codec[l] = 0; }
|
||||
if (r && rate) { r += 6; const char *e = r; while (*e && *e != ' ' && *e != '\n') e++;
|
||||
int l = (int)(e - r); if (l > rlen - 1) l = rlen - 1; memcpy(rate, r, (size_t)l); rate[l] = 0; }
|
||||
if ((codec && codec[0]) || (rate && rate[0])) return;
|
||||
}
|
||||
line = nl ? nl + 1 : NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* 附近设备文本:netmgrd 权威扫描缓存(/tmp/meow_bt_scan.txt),行 "Device <MAC> <Name>"(去重)。 */
|
||||
static void bt_devices_text(char *buf, int cap)
|
||||
{
|
||||
int off = 0;
|
||||
FILE *f = fopen(g_bt_scan_cache, "r");
|
||||
if (!f) { buf[0] = 0; return; }
|
||||
char seen[16][24]; int n_seen = 0;
|
||||
char line[256];
|
||||
while (fgets(line, sizeof line, f) && n_seen < 16) {
|
||||
char *p = line; while (*p == ' ' || *p == '\t') p++;
|
||||
const char *dv = NULL;
|
||||
if (!strncmp(p, "Device ", 7)) dv = p + 7;
|
||||
else if (!strncmp(p, "[NEW] Device ", 13)) dv = p + 13;
|
||||
if (!dv) continue;
|
||||
const char *mac = bt_find_mac(dv);
|
||||
if (!mac) continue;
|
||||
int dup = 0;
|
||||
for (int i = 0; i < n_seen; i++) if (bt_mac_eq(seen[i], mac)) { dup = 1; break; }
|
||||
if (dup) continue;
|
||||
memcpy(seen[n_seen], mac, 17); seen[n_seen][17] = 0; n_seen++;
|
||||
char *nm = (char *)(mac + 17); while (*nm == ' ' || *nm == '\t') nm++;
|
||||
char *e = nm; while (*e && *e != '\n' && *e != '\r') e++;
|
||||
*e = 0;
|
||||
int l = snprintf(buf + off, (size_t)(cap - off), "Device %s %s\n", mac, nm);
|
||||
if (l <= 0 || off + l >= cap) break;
|
||||
off += l;
|
||||
}
|
||||
fclose(f);
|
||||
buf[off] = 0;
|
||||
}
|
||||
|
||||
/* 扫描生命周期(§七:进「附近查找」才 scan on;退页/超时 off)。默认 7s 节流。 */
|
||||
static long g_bt_scan_ms = 0;
|
||||
static void bt_scan_on(void)
|
||||
{
|
||||
long t = mono_now();
|
||||
if (g_bt_scan_ms && t - g_bt_scan_ms < 7000) return;
|
||||
g_bt_scan_ms = t;
|
||||
char cmd[256];
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"(sleep 1; echo 'scan on'; sleep 4; echo 'scan off'; echo 'devices'; echo 'quit') | "
|
||||
"bluetoothctl > '%s' 2>&1 &", g_bt_scan_cache);
|
||||
system(cmd);
|
||||
}
|
||||
static void bt_scan_off(void)
|
||||
{
|
||||
system("PATH=/bin:/sbin:/usr/bin:/usr/sbin bluetoothctl scan off 2>/dev/null");
|
||||
}
|
||||
|
||||
/* ---------------- rfkill:统一软开关 ---------------- */
|
||||
@@ -974,6 +1260,11 @@ static void build_status_json(char *out, size_t cap)
|
||||
bt.powered ? "true" : "false");
|
||||
jadd_str_or_null(&w, "address", bt.address);
|
||||
jadd_int_or_null(&w, "paired", bt.paired_n);
|
||||
jadd_int_or_null(&w, "connected", bt.connected_n);
|
||||
{ char ac[16] = "", ar[16] = "";
|
||||
bt_a2dp_summary(ac, sizeof ac, ar, sizeof ar);
|
||||
jadd_str_or_null(&w, "a2dp_codec", ac);
|
||||
jadd_str_or_null(&w, "a2dp_rate", ar); }
|
||||
jadd(&w, "\"dns\":[");
|
||||
{
|
||||
char ns[DNS_MAX][40];
|
||||
@@ -2326,8 +2617,9 @@ static int wifi_cmd_is_write(const char *cmd, const char *arg)
|
||||
}
|
||||
static int bt_cmd_is_write(const char *cmd)
|
||||
{
|
||||
if (!strcmp(cmd, "status") || !strcmp(cmd, "paired")) return 0;
|
||||
return 1; /* on/off */
|
||||
if (!strcmp(cmd, "status") || !strcmp(cmd, "paired") ||
|
||||
!strcmp(cmd, "connected") || !strcmp(cmd, "a2dp") || !strcmp(cmd, "devices")) return 0;
|
||||
return 1; /* on/off/connect/disconnect/pair/unpair/scan */
|
||||
}
|
||||
|
||||
/* 写级授权决策(纯函数,宿主自测用;handle_conn 复用):读级任意;写级仅系统身份 + MeowOS 模式。
|
||||
@@ -2597,14 +2889,13 @@ static void bt_onoff(const char *cmd, ctl_result_t *r)
|
||||
|
||||
static void bt_ctl_execute(const char *cmd, const char *arg, ctl_result_t *r)
|
||||
{
|
||||
(void)arg;
|
||||
ctl_reset(r, MEOW_OK);
|
||||
if (!strcmp(cmd, "status")) {
|
||||
bt_t b;
|
||||
bt_status(&b);
|
||||
char msg[192];
|
||||
snprintf(msg, sizeof msg, "bt available=%d powered=%d address=%s paired=%d",
|
||||
b.available, b.powered, b.address[0] ? b.address : "-", b.paired_n);
|
||||
snprintf(msg, sizeof msg, "bt available=%d powered=%d address=%s paired=%d connected=%d",
|
||||
b.available, b.powered, b.address[0] ? b.address : "-", b.paired_n, b.connected_n);
|
||||
ctl_sets(r, MEOW_OK, msg);
|
||||
} else if (!strcmp(cmd, "on") || !strcmp(cmd, "off")) {
|
||||
bt_onoff(cmd, r);
|
||||
@@ -2615,6 +2906,33 @@ static void bt_ctl_execute(const char *cmd, const char *arg, ctl_result_t *r)
|
||||
int n = run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd2);
|
||||
if (n <= 0) ctl_sets(r, MEOW_ERR_NODEV, "bt paired: 无输出 (bluetoothctl 不可用/未开)");
|
||||
else ctl_sets(r, MEOW_OK, buf);
|
||||
} else if (!strcmp(cmd, "connected")) {
|
||||
char buf[MEOW_IPC_MAX - 16];
|
||||
bt_connected_text(buf, sizeof buf);
|
||||
ctl_sets(r, MEOW_OK, buf);
|
||||
} else if (!strcmp(cmd, "a2dp")) {
|
||||
char buf[MEOW_IPC_MAX - 16];
|
||||
bt_a2dp_text(buf, sizeof buf);
|
||||
ctl_sets(r, MEOW_OK, buf);
|
||||
} else if (!strcmp(cmd, "devices")) {
|
||||
char buf[MEOW_IPC_MAX - 16];
|
||||
bt_devices_text(buf, sizeof buf);
|
||||
ctl_sets(r, MEOW_OK, buf);
|
||||
} else if (!strcmp(cmd, "connect") || !strcmp(cmd, "disconnect") ||
|
||||
!strcmp(cmd, "pair") || !strcmp(cmd, "unpair")) {
|
||||
if (!arg || !arg[0]) { char m[48]; snprintf(m, sizeof m, "bt %s: 需 <mac>", cmd); ctl_sets(r, MEOW_ERR_INVAL, m); return; }
|
||||
const char *sub = (!strcmp(cmd, "unpair")) ? "remove" : cmd; /* bluetoothctl 取消配对=remove */
|
||||
char cmd2[192];
|
||||
snprintf(cmd2, sizeof cmd2, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s %s %s 2>/dev/null",
|
||||
g_bluetoothctl, sub, arg);
|
||||
char buf[256];
|
||||
int n = run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd2);
|
||||
if (n >= 0) ctl_sets(r, MEOW_OK, buf[0] ? buf : "bt cmd: 已下发");
|
||||
else ctl_sets(r, MEOW_ERR_NODEV, "bt cmd: 后端执行失败");
|
||||
} else if (!strcmp(cmd, "scan")) {
|
||||
if (!arg || !strcmp(arg, "on")) { bt_scan_on(); ctl_sets(r, MEOW_OK, "bt scan on: 后台节流扫描已触发"); }
|
||||
else if (!strcmp(arg, "off")) { bt_scan_off(); ctl_sets(r, MEOW_OK, "bt scan off: 扫描已停"); }
|
||||
else ctl_sets(r, MEOW_ERR_INVAL, "bt scan: 用法 on|off");
|
||||
} else {
|
||||
ctl_sets(r, MEOW_ERR_INVAL, "bt: 未知子命令");
|
||||
}
|
||||
@@ -3204,9 +3522,15 @@ static int cli_status(void)
|
||||
int bpow = strstr(obj, "\"powered\":true") != NULL;
|
||||
jstr(obj, "address", baddr, sizeof baddr);
|
||||
int bpaired = -1;
|
||||
int bconn = -1;
|
||||
jint(obj, "paired", &bpaired);
|
||||
printf("bt available=%d powered=%d address=%s paired=%d\n",
|
||||
bavail, bpow, baddr[0] ? baddr : "-", bpaired);
|
||||
jint(obj, "connected", &bconn);
|
||||
char bcodec[24] = "", brate[24] = "";
|
||||
jstr(obj, "a2dp_codec", bcodec, sizeof bcodec);
|
||||
jstr(obj, "a2dp_rate", brate, sizeof brate);
|
||||
printf("bt available=%d powered=%d address=%s paired=%d connected=%d a2dp=%s@%s\n",
|
||||
bavail, bpow, baddr[0] ? baddr : "-", bpaired, bconn,
|
||||
bcodec[0] ? bcodec : "-", brate[0] ? brate : "-");
|
||||
}
|
||||
/* rfkill 数组 */
|
||||
{
|
||||
@@ -3381,7 +3705,8 @@ static int cli_policy(int op, const char *entity, const char *arg)
|
||||
static const char *usage(void)
|
||||
{
|
||||
return "status | iface [name] | wifi scan|networks|connect <ssid>[,psk]|disconnect|on|off"
|
||||
" | bt status|on|off|paired | rfkill [<type> <on|off>] | udhcpc renew|release [if]"
|
||||
" | bt status|on|off|paired|connected|a2dp|devices|scan on|off|connect|disconnect|pair|unpair <mac>"
|
||||
" | rfkill [<type> <on|off>] | udhcpc renew|release [if]"
|
||||
" | apps | conns [entity] | top | dns status|stats|flush"
|
||||
" | policy show [entity] | policy set <entity> allow|block | policy domain <entity> +host|-host | policy del <entity>"
|
||||
" | help";
|
||||
@@ -3444,6 +3769,15 @@ static int cli_run(int n, char **v)
|
||||
}
|
||||
if (!strcmp(cmd, "bt")) {
|
||||
if (!arg) { fprintf(stderr, "bt 子命令缺失(%s)\n", usage()); return 1; }
|
||||
if (!strcmp(arg, "connect") || !strcmp(arg, "disconnect") ||
|
||||
!strcmp(arg, "pair") || !strcmp(arg, "unpair")) {
|
||||
if (!arg2) { fprintf(stderr, "bt %s 需 <mac>\n", arg); return 1; }
|
||||
return cli_cmd_result(MSG_NET_BT_CTL, arg, arg2);
|
||||
}
|
||||
if (!strcmp(arg, "scan")) {
|
||||
if (arg2 && !strcmp(arg2, "off")) return cli_cmd_result(MSG_NET_BT_CTL, "scan", "off");
|
||||
return cli_cmd_result(MSG_NET_BT_CTL, "scan", "on");
|
||||
}
|
||||
return cli_cmd_result(MSG_NET_BT_CTL, arg, NULL);
|
||||
}
|
||||
if (!strcmp(cmd, "rfkill")) {
|
||||
@@ -3480,6 +3814,9 @@ int main(int argc, char **argv)
|
||||
if ((e = getenv("MEOW_WPA_CLI")) && e[0]) g_wpa_cli = e;
|
||||
if ((e = getenv("MEOW_HCICONFIG")) && e[0]) g_hciconfig = e;
|
||||
if ((e = getenv("MEOW_BLUETOOTHCTL")) && e[0]) g_bluetoothctl = e;
|
||||
if ((e = getenv("MEOW_HCITOOL")) && e[0]) g_hcitool = e;
|
||||
if ((e = getenv("MEOW_BLUEALSA_CLI")) && e[0]) g_bluealsa_cli = e;
|
||||
if ((e = getenv("MEOW_BT_SCAN_CACHE")) && e[0]) g_bt_scan_cache = e;
|
||||
if ((e = getenv("MEOW_IP")) && e[0]) g_ip = e;
|
||||
if ((e = getenv("MEOW_WIFI_IF")) && e[0]) g_wifi_if = e;
|
||||
if ((e = getenv("MEOW_NET_SOCK")) && e[0]) g_sock = e;
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
/* test_bt_ctl.c — meow_netmgrd 蓝牙 P1 权威收口 纯逻辑宿主自测(本程序非生产代码)。
|
||||
*
|
||||
* 通过 #include 生产源文件(meow_netmgrd.c,用 #define main 改名避免入口冲突),同一 TU 直接访问
|
||||
* FILE-STATIC 函数/全局:bt_cmd_is_write / bt_write_authorize / bt_ctl_execute(connect/disconnect/
|
||||
* pair/unpair/connected/a2dp/devices/status)。工具经桩脚本(g_bluetoothctl/g_hcitool/g_bluealsa_cli/
|
||||
* g_bt_scan_cache 指向 $T/桩)覆盖,不依赖真实蓝牙栈。
|
||||
*
|
||||
* 用法:cc -Wall -Wextra -I../meow_launcherd test_bt_ctl.c -o test_bt_ctl && ./test_bt_ctl
|
||||
*/
|
||||
#define main meow_netmgrd_prod_main_unused
|
||||
#include "meow_netmgrd.c"
|
||||
#undef main
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int g_fail = 0;
|
||||
#define CHECK(cond) do { if (!(cond)) { printf(" FAIL: %s (line %d)\n", #cond, __LINE__); g_fail++; } \
|
||||
else { printf(" ok : %s\n", #cond); } } while (0)
|
||||
|
||||
static void wf(const char *p, const char *s)
|
||||
{
|
||||
FILE *f = fopen(p, "w");
|
||||
if (f) { fputs(s, f); fclose(f); }
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
static const char *T = "/tmp/nmg_bt";
|
||||
char p[192];
|
||||
mkdir(T, 0755);
|
||||
/* 假 /sys/class/bluetooth/hci0(available/powered/address) */
|
||||
{ snprintf(p, sizeof p, "%s/bt", T); mkdir(p, 0755);
|
||||
snprintf(p, sizeof p, "%s/bt/hci0", T); mkdir(p, 0755);
|
||||
snprintf(p, sizeof p, "%s/bt/hci0/address", T); wf(p, "00:1A:7D:DA:71:13\n");
|
||||
snprintf(p, sizeof p, "%s/bt/hci0/power", T); wf(p, "on\n"); }
|
||||
|
||||
/* 桩:bluetoothctl(paired/devices/connect/remove)、hcitool con、bluealsa-cli a2dp、扫描缓存 */
|
||||
snprintf(p, sizeof p, "%s/bluetoothctl", T); wf(p,
|
||||
"#!/bin/sh\ncase \"$1\" in\n"
|
||||
" paired|paired-devices|devices) echo 'Device 33:DE:2E:21:AD:30 Pudding'; echo 'Device 11:22:33:44:55:66 Speaker';;\n"
|
||||
" connect|disconnect|pair|remove) echo 'Controller 00:1A:7D:DA:71:13'; echo 'Device 33:DE:2E:21:AD:30 Connected: yes';;\n"
|
||||
"esac\nexit 0\n"); chmod(p, 0755);
|
||||
snprintf(p, sizeof p, "%s/hcitool", T); wf(p,
|
||||
"#!/bin/sh\necho '\t< ACL 33:DE:2E:21:AD:30 handle 12 state 1 lm MASTER'\n"
|
||||
"echo '\t< ACL 11:22:33:44:55:66 handle 13 state 1 lm SLAVE'\nexit 0\n"); chmod(p, 0755);
|
||||
snprintf(p, sizeof p, "%s/bluealsa-cli", T); wf(p,
|
||||
"#!/bin/sh\nif [ \"${1:-}\" = \"a2dp\" ]; then\n"
|
||||
"echo '/org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/a2dpsrc:'\n"
|
||||
"echo '\tCodec: SBC'\necho '\tSampling: 44100 Hz'\necho '\tChannels: 2'\n"
|
||||
"echo '\tBits per Sample: 16'\necho '\tState: RUNNING'\necho '\tAcquired: yes'\nfi\nexit 0\n"); chmod(p, 0755);
|
||||
snprintf(p, sizeof p, "%s/bt_scan.txt", T); wf(p,
|
||||
"[NEW] Device 33:DE:2E:21:AD:30 Pudding\n[NEW] Device AA:BB:CC:DD:EE:FF Earbuds\n");
|
||||
|
||||
g_bluetoothctl = ""; { char x[160]; snprintf(x, sizeof x, "%s/bluetoothctl", T); static char s[160];
|
||||
snprintf(s, sizeof s, "%s", x); g_bluetoothctl = s; }
|
||||
g_hcitool = ""; { char x[160]; snprintf(x, sizeof x, "%s/hcitool", T); static char s[160];
|
||||
snprintf(s, sizeof s, "%s", x); g_hcitool = s; }
|
||||
g_bluealsa_cli = ""; { char x[160]; snprintf(x, sizeof x, "%s/bluealsa-cli", T); static char s[160];
|
||||
snprintf(s, sizeof s, "%s", x); g_bluealsa_cli = s; }
|
||||
g_bt_scan_cache = ""; { char x[160]; snprintf(x, sizeof x, "%s/bt_scan.txt", T); static char s[160];
|
||||
snprintf(s, sizeof s, "%s", x); g_bt_scan_cache = s; }
|
||||
g_bt_dir[0] = 0; snprintf(g_bt_dir, sizeof g_bt_dir, "%s/bt", T);
|
||||
|
||||
ctl_result_t r;
|
||||
|
||||
printf("== bt_cmd_is_write 分类(读/写级) ==\n");
|
||||
CHECK(bt_cmd_is_write("status") == 0);
|
||||
CHECK(bt_cmd_is_write("paired") == 0);
|
||||
CHECK(bt_cmd_is_write("connected") == 0);
|
||||
CHECK(bt_cmd_is_write("a2dp") == 0);
|
||||
CHECK(bt_cmd_is_write("devices") == 0);
|
||||
CHECK(bt_cmd_is_write("on") == 1);
|
||||
CHECK(bt_cmd_is_write("off") == 1);
|
||||
CHECK(bt_cmd_is_write("connect") == 1);
|
||||
CHECK(bt_cmd_is_write("disconnect") == 1);
|
||||
CHECK(bt_cmd_is_write("pair") == 1);
|
||||
CHECK(bt_cmd_is_write("unpair") == 1);
|
||||
CHECK(bt_cmd_is_write("scan") == 1);
|
||||
|
||||
printf("== bt_ctl_execute 写级(connect/disconnect/pair/unpair,经桩 bluetoothctl) ==\n");
|
||||
bt_ctl_execute("connect", "33:DE:2E:21:AD:30", &r);
|
||||
CHECK(r.code == MEOW_OK);
|
||||
CHECK(strstr(r.text, "Connected") != NULL);
|
||||
bt_ctl_execute("disconnect", "33:DE:2E:21:AD:30", &r);
|
||||
CHECK(r.code == MEOW_OK);
|
||||
bt_ctl_execute("pair", "33:DE:2E:21:AD:30", &r);
|
||||
CHECK(r.code == MEOW_OK);
|
||||
bt_ctl_execute("unpair", "33:DE:2E:21:AD:30", &r);
|
||||
CHECK(r.code == MEOW_OK);
|
||||
CHECK(strstr(r.text, "Connected") != NULL); /* remove 桩回 Connected: yes */
|
||||
bt_ctl_execute("connect", "", &r);
|
||||
CHECK(r.code == MEOW_ERR_INVAL); /* 缺 mac → INVAL */
|
||||
bt_ctl_execute("scan", NULL, &r);
|
||||
CHECK(r.code == MEOW_OK); /* scan on(默认) */
|
||||
CHECK(strstr(r.text, "scan on") != NULL);
|
||||
bt_ctl_execute("scan", "off", &r);
|
||||
CHECK(r.code == MEOW_OK);
|
||||
|
||||
printf("== bt_ctl_execute 读级(connected/a2dp/devices/status,权威) ==\n");
|
||||
bt_ctl_execute("connected", NULL, &r);
|
||||
CHECK(r.code == MEOW_OK);
|
||||
CHECK(strstr(r.text, "Device 33:DE:2E:21:AD:30") != NULL);
|
||||
CHECK(strstr(r.text, "Pudding") != NULL);
|
||||
bt_ctl_execute("a2dp", NULL, &r);
|
||||
CHECK(r.code == MEOW_OK);
|
||||
CHECK(strstr(r.text, "codec=SBC") != NULL);
|
||||
CHECK(strstr(r.text, "rate=44100") != NULL);
|
||||
CHECK(strstr(r.text, "acquired=1") != NULL);
|
||||
CHECK(strstr(r.text, "role=src") != NULL);
|
||||
snprintf(p, sizeof p, "%s/bt_scan.txt", T); wf(p, /* 重写(bt scan on 曾覆盖) */
|
||||
"[NEW] Device 33:DE:2E:21:AD:30 Pudding\n[NEW] Device AA:BB:CC:DD:EE:FF Earbuds\n");
|
||||
bt_ctl_execute("devices", NULL, &r);
|
||||
CHECK(r.code == MEOW_OK);
|
||||
CHECK(strstr(r.text, "Earbuds") != NULL);
|
||||
CHECK(strstr(r.text, "Device 33:DE:2E:21:AD:30") != NULL);
|
||||
bt_ctl_execute("status", NULL, &r);
|
||||
CHECK(r.code == MEOW_OK);
|
||||
CHECK(strstr(r.text, "connected=2") != NULL);
|
||||
|
||||
printf("== bt_write_authorize(系统身份 + MeowOS 才放行写级) ==\n");
|
||||
caller_t other; memset(&other, 0, sizeof other); other.kind = CALL_OTHER;
|
||||
caller_t sys; memset(&sys, 0, sizeof sys); sys.kind = CALL_SYS;
|
||||
snprintf(sys.pkg, sizeof sys.pkg, "meow_net");
|
||||
CHECK(bt_write_authorize(&other, 1, "connect") == MEOW_ERR_PERM); /* 非系统 → 拒 */
|
||||
CHECK(bt_write_authorize(&sys, 1, "connect") == MEOW_OK); /* 系统+meow → 放 */
|
||||
CHECK(bt_write_authorize(&sys, 0, "connect") == MEOW_ERR_PERM); /* 系统+hiby → 拒 */
|
||||
CHECK(bt_write_authorize(&other, 1, "status") == MEOW_OK); /* 读级任意 → 放 */
|
||||
|
||||
if (g_fail) { printf("\nBT_CTL TEST: %d FAIL\n", g_fail); return 1; }
|
||||
printf("\nBT_CTL TEST: ALL PASS\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -75,8 +75,39 @@ echo " UP RUNNING PSCAN"
|
||||
EOF
|
||||
cat > "$T/bluetoothctl" <<'EOF'
|
||||
#!/bin/sh
|
||||
echo "Device 00:1A:7D:DA:71:13 My Headset"
|
||||
echo "Device 11:22:33:44:55:66 Speaker"
|
||||
case "${1:-}" in
|
||||
paired|paired-devices)
|
||||
echo "Device 00:1A:7D:DA:71:13 My Headset"
|
||||
echo "Device 11:22:33:44:55:66 Speaker";;
|
||||
devices)
|
||||
echo "Device 00:1A:7D:DA:71:13 My Headset"
|
||||
echo "Device 11:22:33:44:55:66 Speaker"
|
||||
echo "Device 33:DE:2E:21:AD:30 Pudding";;
|
||||
connect|disconnect|pair|remove)
|
||||
echo "Controller 00:1A:7D:DA:71:13 Connected: yes";;
|
||||
*) echo "Device 00:1A:7D:DA:71:13 My Headset"; echo "Device 11:22:33:44:55:66 Speaker";;
|
||||
esac
|
||||
exit 0
|
||||
EOF
|
||||
cat > "$T/hcitool" <<'EOF'
|
||||
#!/bin/sh
|
||||
echo " < ACL 33:DE:2E:21:AD:30 handle 12 state 1 lm MASTER"
|
||||
echo " < ACL 11:22:33:44:55:66 handle 13 state 1 lm SLAVE"
|
||||
exit 0
|
||||
EOF
|
||||
cat > "$T/bluealsa-cli" <<'EOF'
|
||||
#!/bin/sh
|
||||
if [ "${1:-}" = "a2dp" ]; then
|
||||
echo "/org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/a2dpsrc:"
|
||||
echo " Type: A2DP Source"
|
||||
echo " Codec: SBC"
|
||||
echo " Sampling: 44100 Hz"
|
||||
echo " Channels: 2"
|
||||
echo " Bits per Sample: 16"
|
||||
echo " State: RUNNING"
|
||||
echo " Acquired: yes"
|
||||
fi
|
||||
exit 0
|
||||
EOF
|
||||
cat > "$T/ip" <<'EOF'
|
||||
#!/bin/sh
|
||||
@@ -86,13 +117,14 @@ for a in "$@"; do
|
||||
done
|
||||
exit 0
|
||||
EOF
|
||||
for f in wpa_cli hciconfig bluetoothctl ip; do chmod +x "$T/$f"; done
|
||||
for f in wpa_cli hciconfig bluetoothctl hcitool bluealsa-cli ip; do chmod +x "$T/$f"; done
|
||||
|
||||
mkdir -p "$T/logs" "$T/pid"
|
||||
# 期2:DNS 禁用(宿主无 root 不绑 53),观测层经 MEOW_TCP/UDP 打桩;amsd/svcd 缺省(实体归因走 proc:comm→unknown)。
|
||||
ENVS="MEOW_NET_DIR=$NET MEOW_NETDEV=$T/netdev MEOW_NET_ROUTE=$T/route MEOW_RESOLV=$T/resolv
|
||||
MEOW_RFKILL_DIR=$RF MEOW_BT_DIR=$BT MEOW_BOOT_MODE=$T/boot_mode MEOW_BOOTTS_CACHE=$T/bootts
|
||||
MEOW_IP=$T/ip MEOW_WPA_CLI=$T/wpa_cli MEOW_HCICONFIG=$T/hciconfig MEOW_BLUETOOTHCTL=$T/bluetoothctl
|
||||
MEOW_HCITOOL=$T/hcitool MEOW_BLUEALSA_CLI=$T/bluealsa-cli MEOW_BT_SCAN_CACHE=$T/bt_scan.txt
|
||||
MEOW_NET_SOCK=$T/net.sock MEOW_NET_POLL_SEC=1 MEOW_LOG_DIR=$T/logs
|
||||
MEOW_TCP=$T/tcp MEOW_UDP=$T/udp MEOW_NET_POLICY=$T/policy
|
||||
MEOW_AMS_SOCK=$T/no_ams MEOW_SVCD_SOCK=$T/no_svcd MEOW_DNS_DISABLE=1"
|
||||
@@ -137,10 +169,40 @@ ck T4a "permission denied" "$OUT"
|
||||
OUT=$(env $ENVS $Q "$T/meow_net" bt status 2>&1)
|
||||
ck T5a "available=1 powered=1" "$OUT"
|
||||
ck T5b "paired=2" "$OUT"
|
||||
ck T5b2 "connected=2" "$OUT" # P1:内核 ACL 权威已连接数
|
||||
OUT=$(env $ENVS $Q "$T/meow_net" bt paired 2>&1)
|
||||
ck T5c "My Headset" "$OUT"
|
||||
ck T5d "Speaker" "$OUT"
|
||||
|
||||
# ---- T5e: bt connected(P1:内核 ACL 权威,名经 devices 缓存兜底)----
|
||||
OUT=$(env $ENVS $Q "$T/meow_net" bt connected 2>&1)
|
||||
ck T5e "Device 33:DE:2E:21:AD:30 Pudding" "$OUT"
|
||||
ck T5e2 "Device 11:22:33:44:55:66 Speaker" "$OUT"
|
||||
|
||||
# ---- T5f: bt a2dp(P1:bluealsa-cli a2dp list → codec/rate/acquired/role)----
|
||||
OUT=$(env $ENVS $Q "$T/meow_net" bt a2dp 2>&1)
|
||||
ck T5f "codec=SBC" "$OUT"
|
||||
ck T5f2 "rate=44100" "$OUT"
|
||||
ck T5f3 "acquired=1" "$OUT"
|
||||
ck T5f4 "role=src" "$OUT"
|
||||
|
||||
# ---- T5g: bt devices(P1:netmgrd 权威扫描缓存)----
|
||||
cat > "$T/bt_scan.txt" <<'EOF'
|
||||
[NEW] Device 33:DE:2E:21:AD:30 Pudding
|
||||
[NEW] Device AA:BB:CC:DD:EE:FF Earbuds
|
||||
[DEL] Device 00:11:22:33:44:55
|
||||
EOF
|
||||
OUT=$(env $ENVS $Q "$T/meow_net" bt devices 2>&1)
|
||||
ck T5g "Device 33:DE:2E:21:AD:30 Pudding" "$OUT"
|
||||
ck T5g2 "Device AA:BB:CC:DD:EE:FF Earbuds" "$OUT"
|
||||
|
||||
# ---- T5h: bt 写级(connect/pair 等)在宿主 CLI=CALL_OTHER → 拒(仅系统身份+MeowOS 放行,见 T6)----
|
||||
OUT=$(env $ENVS $Q "$T/meow_net" bt connect 33:DE:2E:21:AD:30 2>&1); RC=$?
|
||||
ck T5h "permission denied" "$OUT"
|
||||
ck T5h2 "rc=1" "rc=$RC"
|
||||
OUT=$(env $ENVS $Q "$T/meow_net" bt scan on 2>&1)
|
||||
ck T5i "permission denied" "$OUT"
|
||||
|
||||
# ---- T6: HibyOS 只读降级(读可查,写拒)----
|
||||
echo hiby > "$T/boot_mode"
|
||||
sleep 2 # 等守护下一拍热切换
|
||||
|
||||
Reference in new issue
Block a user