631 lines
25 KiB
C
631 lines
25 KiB
C
/* netmgr_ipc.c — MeowOS launcher → meow_netmgrd IPC 客户端(M6.0 设置页迁移,NETMGR_DESIGN §12)。
|
||
*
|
||
* 【迁移注释】wifi_page/bt_page 的 WiFi/BT 查询与操作统一走 netmgrd IPC(socket
|
||
* /usr/data/meow_net.sock,env MEOW_NET_SOCK 覆盖)。netmgrd 是 WiFi/BT 操作权威
|
||
* (IPC 0xE4 WIFI_CTL / 0xE6 BT_CTL);本文件是「设置页客户端 → 权威 IPC」的唯一入口。
|
||
* 【双轨过渡】netmgrd 不可达(NETMGR_UNREACH)时,本文件回退到 net.c 既有的直连
|
||
* wpa_cli/hciconfig/busybox 读取(保功能);状态栏物理键语义、sdk/amsd/netmgrd 本体不动。
|
||
*
|
||
* 鉴权:跟 pm_power_ipc 同款 —— 先 MSG_HELLO(app_id=os.meow.launcher,netmgrd 按
|
||
* SO_PEERCRED 认出 /usr/bin/meow_launcherd = 系统身份,写级放行),再发 CTL,收 RESULT。
|
||
* 只读观测(wlan_connected()/hci_powered() 读 /sys 链路态)仍在 net.c;此处只接权威操作。
|
||
*/
|
||
#include "meow_launcher_internal.h"
|
||
|
||
#define NETMGR_SOCK_DEFAULT "/usr/data/meow_net.sock"
|
||
/* NETMGR_UNREACH 定义在 meow_launcher_internal.h(测试/调用方共用) */
|
||
|
||
/* 可达性缓存:netmgrd 不可达时每次 connect 都秒回 ECONNREFUSED,无成本;缓存约 1s 防热循环。 */
|
||
static int g_nm_ok = -1; /* -1 未知 / 0 down / 1 up */
|
||
static time_t g_nm_ok_ts = 0;
|
||
|
||
static void nm_cache(int ok) { g_nm_ok = ok; g_nm_ok_ts = time(NULL); }
|
||
|
||
/* ---- socket 连接 ---- */
|
||
static int nm_sock(void)
|
||
{
|
||
const char *sock = getenv("MEOW_NET_SOCK");
|
||
if (!sock || !sock[0]) sock = NETMGR_SOCK_DEFAULT;
|
||
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||
if (fd < 0) return -1;
|
||
struct sockaddr_un a;
|
||
memset(&a, 0, sizeof a);
|
||
a.sun_family = AF_UNIX;
|
||
strncpy(a.sun_path, sock, sizeof a.sun_path - 1);
|
||
if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { close(fd); return -1; }
|
||
return fd;
|
||
}
|
||
|
||
/* ---- 帧收发(复刻 power_menu.c pw_xchg:读超时 1.5s,绝不让子页主循环卡死)---- */
|
||
static int nm_w_all(int fd, const uint8_t *b, size_t n)
|
||
{
|
||
size_t off = 0;
|
||
while (off < n) { ssize_t w = write(fd, b + off, n - off); if (w <= 0) return -1; off += (size_t)w; }
|
||
return 0;
|
||
}
|
||
static int nm_r_all(int fd, uint8_t *b, size_t n)
|
||
{
|
||
size_t off = 0;
|
||
while (off < n) { ssize_t r = read(fd, b + off, n - off); if (r <= 0) return -1; off += (size_t)r; }
|
||
return 0;
|
||
}
|
||
static int nm_xchg(int fd, uint16_t type, uint32_t req, const void *pl, uint32_t len,
|
||
uint8_t *rbuf, uint32_t rcap, uint16_t *rtype)
|
||
{
|
||
uint8_t hdr[MEOW_IPC_HDR_LEN];
|
||
uint32_t magic = MEOW_IPC_MAGIC, rlen = len;
|
||
uint16_t ver = MEOW_IPC_VERSION, t = type;
|
||
memcpy(hdr + 0, &magic, 4); memcpy(hdr + 4, &ver, 2); memcpy(hdr + 6, &t, 2);
|
||
memcpy(hdr + 8, &req, 4); memcpy(hdr + 12, &rlen, 4);
|
||
if (nm_w_all(fd, hdr, sizeof hdr) < 0) return -1;
|
||
if (len && pl && nm_w_all(fd, pl, len) < 0) return -1;
|
||
struct pollfd pf = { fd, POLLIN, 0 };
|
||
int pr = poll(&pf, 1, 1500);
|
||
if (pr <= 0) return -1;
|
||
uint8_t rhdr[MEOW_IPC_HDR_LEN];
|
||
if (nm_r_all(fd, rhdr, sizeof rhdr) < 0) return -1;
|
||
uint32_t rmagic, rlen2; uint16_t rver, rt;
|
||
memcpy(&rmagic, rhdr, 4); memcpy(&rver, rhdr + 4, 2); memcpy(&rt, rhdr + 6, 2);
|
||
memcpy(&rlen2, rhdr + 12, 4);
|
||
if (rmagic != MEOW_IPC_MAGIC || rver != MEOW_IPC_VERSION) return -1;
|
||
if (rlen2 > rcap) rlen2 = rcap;
|
||
if (rlen2 && nm_r_all(fd, rbuf, rlen2) < 0) return -1;
|
||
if (rtype) *rtype = rt;
|
||
return 0;
|
||
}
|
||
|
||
static int nm_hello(int fd)
|
||
{
|
||
m_hello_t h;
|
||
memset(&h, 0, sizeof h);
|
||
snprintf(h.app_id, sizeof h.app_id, "%s", "os.meow.launcher");
|
||
h.version = 1;
|
||
uint8_t rbuf[256]; uint16_t rtype = 0;
|
||
if (nm_xchg(fd, MSG_HELLO, 0x100, &h, sizeof h, rbuf, sizeof rbuf, &rtype) < 0 || rtype != MSG_ACK)
|
||
return -1;
|
||
m_ack_t ak; memcpy(&ak, rbuf, sizeof ak);
|
||
return (ak.code == MEOW_OK) ? 0 : -1;
|
||
}
|
||
|
||
/* 发一条 WIFI_CTL/BT_CTL 子命令,收 RESULT。返回 netmgrd 业务 code;NETMGR_UNREACH=不可达。 */
|
||
static int nm_cmd(uint16_t ctl_type, uint16_t res_type, const char *cmd, const char *arg,
|
||
char *text, int tlen)
|
||
{
|
||
m_net_cmd_t q;
|
||
memset(&q, 0, sizeof q);
|
||
snprintf(q.cmd, sizeof q.cmd, "%s", cmd ? cmd : "");
|
||
if (arg) snprintf(q.arg, sizeof q.arg, "%s", arg);
|
||
int fd = nm_sock();
|
||
if (fd < 0) { nm_cache(0); return NETMGR_UNREACH; }
|
||
if (nm_hello(fd) < 0) { close(fd); nm_cache(0); return NETMGR_UNREACH; }
|
||
uint8_t rbuf[MEOW_IPC_MAX]; uint16_t rtype = 0;
|
||
memset(rbuf, 0, sizeof rbuf);
|
||
if (nm_xchg(fd, ctl_type, 0x101, &q, sizeof q, rbuf, sizeof rbuf, &rtype) < 0 || rtype != res_type) {
|
||
close(fd); nm_cache(0); return NETMGR_UNREACH;
|
||
}
|
||
m_net_cmd_result_t res; memcpy(&res, rbuf, sizeof res);
|
||
close(fd);
|
||
nm_cache(1);
|
||
uint32_t tl = res.text_len;
|
||
if (tl >= (uint32_t)tlen) tl = (uint32_t)(tlen - 1);
|
||
if (text && tlen > 0) { memcpy(text, res.text, tl); text[tl] = 0; }
|
||
return (int)res.code;
|
||
}
|
||
|
||
int netmgr_wifi(const char *cmd, const char *arg, char *text, int tlen)
|
||
{ return nm_cmd(MSG_NET_WIFI_CTL, MSG_NET_WIFI_RESULT, cmd, arg, text, tlen); }
|
||
|
||
int netmgr_bt(const char *cmd, const char *arg, char *text, int tlen)
|
||
{ return nm_cmd(MSG_NET_BT_CTL, MSG_NET_BT_RESULT, cmd, arg, text, tlen); }
|
||
|
||
int netmgr_ok(void)
|
||
{
|
||
if (g_nm_ok >= 0 && time(NULL) - g_nm_ok_ts < 1) return g_nm_ok;
|
||
int fd = nm_sock();
|
||
if (fd < 0) { nm_cache(0); return 0; }
|
||
close(fd); nm_cache(1); return 1;
|
||
}
|
||
|
||
|
||
/* ================= WiFi(IPC 优先,net.c 直连回退)================= */
|
||
|
||
/* —— net.c 的 wpa_decode_ssid 是 static,这里复制最小实现(本模块自含,避免改 net.c 可见性)—— */
|
||
static int nm_hex(char 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 void nm_decode_ssid(const char *src, char *dst, int dlen)
|
||
{
|
||
int o = 0;
|
||
while (*src && o < dlen - 1) {
|
||
if (src[0] == '\\' && src[1] == 'x' && nm_hex(src[2]) >= 0 && nm_hex(src[3]) >= 0) {
|
||
dst[o++] = (char)((nm_hex(src[2]) << 4) | nm_hex(src[3]));
|
||
src += 4;
|
||
} else {
|
||
dst[o++] = *src++;
|
||
}
|
||
}
|
||
dst[o] = 0;
|
||
}
|
||
|
||
/* P1:拷一个「空格分隔的值 token」到 dst(a2dp 字段 extraction 用)。 */
|
||
static void nm_val_field(char **p, char *dst, int dlen)
|
||
{
|
||
while (**p == ' ' || **p == '\t') (*p)++;
|
||
int o = 0;
|
||
while (**p && **p != ' ' && **p != '\t' && **p != '\n' && o < dlen - 1) dst[o++] = *(*p)++;
|
||
dst[o] = 0;
|
||
}
|
||
|
||
/* —— netmgrd `wifi status` 单行摘要解析:`wifi state=X connected=N ssid=S ip=I rssi=R`。
|
||
* 已知字段以「空格+key=」分隔;ssid 可能含空格,需读到下一个已知字段为止。 —— */
|
||
static int nm_is_status_key(const char *p)
|
||
{
|
||
if (!strncmp(p, " ip=", 4) || !strncmp(p, " rssi=", 6) ||
|
||
!strncmp(p, " connected=", 11) || !strncmp(p, " state=", 7)) return 1;
|
||
return 0;
|
||
}
|
||
static const char *nm_val(const char *text, const char *key, char *dst, int dlen, int space_ok)
|
||
{
|
||
const char *p = strstr(text, key);
|
||
if (!p) { dst[0] = 0; return p; }
|
||
p += strlen(key);
|
||
int o = 0;
|
||
while (*p && o < dlen - 1) {
|
||
if (*p == ' ') {
|
||
if (!space_ok) break;
|
||
if (nm_is_status_key(p)) break; /* ssid 含空格:停在下一个已知字段 */
|
||
}
|
||
dst[o++] = *p++;
|
||
}
|
||
dst[o] = 0;
|
||
return p;
|
||
}
|
||
static int nm_parse_connected(const char *text)
|
||
{
|
||
char v[8];
|
||
nm_val(text, "connected=", v, sizeof v, 0);
|
||
return atoi(v);
|
||
}
|
||
|
||
int nm_wifi_connected(void)
|
||
{
|
||
char text[256];
|
||
int rc = netmgr_wifi("status", NULL, text, sizeof text);
|
||
if (rc == NETMGR_UNREACH) return wlan_connected();
|
||
if (rc != MEOW_OK) return 0;
|
||
return nm_parse_connected(text);
|
||
}
|
||
|
||
void nm_wifi_ssid(char *buf, int len)
|
||
{
|
||
buf[0] = 0;
|
||
char text[256];
|
||
int rc = netmgr_wifi("status", NULL, text, sizeof text);
|
||
if (rc == NETMGR_UNREACH) { net_wifi_ssid(buf, len); return; }
|
||
if (rc != MEOW_OK) return;
|
||
if (!nm_parse_connected(text)) return;
|
||
char raw[128];
|
||
nm_val(text, "ssid=", raw, sizeof raw, 1);
|
||
nm_decode_ssid(raw, buf, len);
|
||
}
|
||
|
||
int nm_wifi_info(char (*info)[48], int max)
|
||
{
|
||
char text[256];
|
||
int rc = netmgr_wifi("status", NULL, text, sizeof text);
|
||
if (rc == NETMGR_UNREACH) return net_wifi_info(info, max); /* 回退:完整详情(直连 wpa_cli+ip route) */
|
||
if (rc != MEOW_OK || !nm_parse_connected(text)) return 0;
|
||
char ssid[40] = "", ip[32] = "", rssi[16] = "";
|
||
char raw[128];
|
||
nm_val(text, "ssid=", raw, sizeof raw, 1);
|
||
nm_decode_ssid(raw, ssid, sizeof ssid);
|
||
nm_val(text, "ip=", ip, sizeof ip, 0);
|
||
nm_val(text, "rssi=", rssi, sizeof rssi, 0);
|
||
int n = 0;
|
||
if (ssid[0]) snprintf(info[n++], 48, "SSID: %s", ssid);
|
||
if (ip[0]) snprintf(info[n++], 48, "本机IP: %s", ip);
|
||
if (rssi[0]) snprintf(info[n++], 48, "信号: %s dBm", rssi);
|
||
if (n == 0) snprintf(info[n++], 48, "已连接(无详情)");
|
||
return n;
|
||
}
|
||
|
||
/* —— 解析 netmgrd `wifi networks`(wpa_cli list_networks 原样文本)→ meow_net_t[];
|
||
* 直接复用 net.c 既有表头跳行逻辑。返回项数。 —— */
|
||
static int nm_parse_saved(const char *txt, meow_net_t *out, int max)
|
||
{
|
||
int n = 0;
|
||
if (!txt || max <= 0) return 0;
|
||
if (max > S_NET_MAX) max = S_NET_MAX;
|
||
const char *line = strchr(txt, '\n');
|
||
if (line) line++;
|
||
while (line && *line && n < max) {
|
||
const char *eol = strchr(line, '\n');
|
||
const char *p = line; while (*p == ' ' || *p == '\t') p++;
|
||
if (*p && *p != '#') {
|
||
int id = atoi(p);
|
||
const char *sp = p; while (*sp && *sp != ' ' && *sp != '\t') sp++;
|
||
while (*sp == ' ' || *sp == '\t') sp++;
|
||
const char *se = sp; while (*se && *se != ' ' && *se != '\t' && *se != '\n' && *se != '\r') se++;
|
||
const char *bp = se; while (*bp == ' ' || *bp == '\t') bp++;
|
||
const char *be = bp; while (*be && *be != ' ' && *be != '\t' && *be != '\n' && *be != '\r') be++;
|
||
if (sp != se) {
|
||
char raw[160];
|
||
int l = (int)(se - sp);
|
||
if (l > (int)sizeof raw - 1) l = (int)sizeof raw - 1;
|
||
memcpy(raw, sp, (size_t)l); raw[l] = 0;
|
||
out[n].id = id;
|
||
nm_decode_ssid(raw, out[n].name, sizeof out[n].name);
|
||
if (be > bp && (be - bp) < (int)sizeof out[n].mac) {
|
||
memcpy(out[n].mac, bp, (size_t)(be - bp)); out[n].mac[be - bp] = 0;
|
||
}
|
||
out[n].sec[0] = 0;
|
||
n++;
|
||
}
|
||
}
|
||
line = eol ? eol + 1 : NULL;
|
||
}
|
||
return n;
|
||
}
|
||
|
||
/* —— 解析 netmgrd `wifi scan`(wpa_cli scan_results 原样文本)→ meow_net_t[](按 BSSID 去重、
|
||
* 优先具名;补充 sec 安全类型供「新网络录入」判断是否弹 IME)。返回项数。 —— */
|
||
static int nm_parse_scan(const char *txt, meow_net_t *out, int max)
|
||
{
|
||
int n = 0;
|
||
if (!txt || max <= 0) return 0;
|
||
if (max > S_NET_MAX) max = S_NET_MAX;
|
||
char seen_bssid[S_NET_MAX][24];
|
||
int named[S_NET_MAX] = {0};
|
||
const char *line = strchr(txt, '\n');
|
||
if (line) line++;
|
||
while (line && *line && n < max) {
|
||
const char *eol = strchr(line, '\n');
|
||
const char *p = line; while (*p == ' ' || *p == '\t') p++;
|
||
if (*p) {
|
||
char bssid[24] = "";
|
||
{ const char *b = p; while (*p && *p != ' ' && *p != '\t') p++;
|
||
int l = (int)(p - b); if (l > 22) l = 22; memcpy(bssid, b, (size_t)l); bssid[l] = 0; }
|
||
const char *ssid = "";
|
||
char sec[16] = "Open";
|
||
const char *br = strchr(p, '[');
|
||
if (br) {
|
||
/* 安全类型 = 首个方括号内容([WPA-PSK-CCMP] 等);纯 [ESS]/[IBSS] = 开放 */
|
||
const char *be = strchr(br + 1, ']');
|
||
if (be) {
|
||
int sl = (int)(be - (br + 1));
|
||
if (sl > 15) sl = 15;
|
||
if (sl > 0) {
|
||
memcpy(sec, br + 1, (size_t)sl); sec[sl] = 0;
|
||
if (!strstr(sec, "WPA") && !strstr(sec, "RSN") &&
|
||
!strstr(sec, "WEP") && !strstr(sec, "SAE") && !strstr(sec, "EAP"))
|
||
snprintf(sec, sizeof sec, "Open");
|
||
}
|
||
}
|
||
const char *q = br;
|
||
while (*q) {
|
||
while (*q == ' ' || *q == '\t') q++;
|
||
if (*q == '[') { while (*q && *q != ' ' && *q != '\t') q++; }
|
||
else break;
|
||
}
|
||
ssid = q; while (*ssid == ' ' || *ssid == '\t') ssid++;
|
||
}
|
||
const char *se = ssid;
|
||
while (*se && *se != '\n' && *se != '\r') se++;
|
||
while (se > ssid && (se[-1] == ' ' || se[-1] == '\t')) se--;
|
||
int has = (se > ssid && *ssid);
|
||
int idx = -1;
|
||
for (int j = 0; j < n; j++) if (!strcmp(seen_bssid[j], bssid)) { idx = j; break; }
|
||
if (idx < 0) {
|
||
if (n >= max) { line = eol ? eol + 1 : NULL; continue; }
|
||
idx = n++;
|
||
strncpy(seen_bssid[idx], bssid, sizeof seen_bssid[idx] - 1);
|
||
seen_bssid[idx][sizeof seen_bssid[idx] - 1] = 0;
|
||
named[idx] = has;
|
||
out[idx].id = -1;
|
||
snprintf(out[idx].mac, sizeof out[idx].mac, "%s", bssid);
|
||
snprintf(out[idx].sec, sizeof out[idx].sec, "%s", sec);
|
||
if (has) {
|
||
char raw[96];
|
||
int l = (int)(se - ssid); if (l > (int)sizeof raw - 1) l = (int)sizeof raw - 1;
|
||
memcpy(raw, ssid, (size_t)l); raw[l] = 0;
|
||
nm_decode_ssid(raw, out[idx].name, sizeof out[idx].name);
|
||
} else {
|
||
snprintf(out[idx].name, sizeof out[idx].name, "(隐藏网络)");
|
||
}
|
||
} else if (has && !named[idx]) {
|
||
char raw[96];
|
||
int l = (int)(se - ssid); if (l > (int)sizeof raw - 1) l = (int)sizeof raw - 1;
|
||
memcpy(raw, ssid, (size_t)l); raw[l] = 0;
|
||
nm_decode_ssid(raw, out[idx].name, sizeof out[idx].name);
|
||
named[idx] = 1;
|
||
}
|
||
}
|
||
line = eol ? eol + 1 : NULL;
|
||
}
|
||
return n;
|
||
}
|
||
|
||
int nm_wifi_saved(meow_net_t *out, int max)
|
||
{
|
||
char text[MEOW_IPC_MAX - 32];
|
||
int rc = netmgr_wifi("networks", NULL, text, sizeof text);
|
||
if (rc == NETMGR_UNREACH) return net_wifi_saved(out, max); /* 直连回退 */
|
||
if (rc != MEOW_OK) return 0;
|
||
return nm_parse_saved(text, out, max);
|
||
}
|
||
|
||
int nm_wifi_scan(meow_net_t *out, int max)
|
||
{
|
||
char text[MEOW_IPC_MAX - 32];
|
||
int rc = netmgr_wifi("scan", NULL, text, sizeof text); /* netmgrd 内含触发+有界等待+读结果 */
|
||
if (rc == NETMGR_UNREACH) return net_wifi_scan(out, max);
|
||
if (rc != MEOW_OK) return 0;
|
||
return nm_parse_scan(text, out, max);
|
||
}
|
||
|
||
void nm_wifi_scan_trigger(void)
|
||
{
|
||
/* IPC:scan 子命令在 netmgrd 内已一次性「触发+等待+读结果」,此处无需单独触发;
|
||
* 直连回退:异步 wpa_cli scan(结果稍后 net_wifi_scan 读)。 */
|
||
if (!netmgr_ok()) net_wifi_scan_trigger();
|
||
}
|
||
|
||
/* —— 直连回退:wpa_cli add/set/enable/select 新网络(复刻 netmgrd wifi_connect 逻辑)—— */
|
||
static int nm_direct_connect(const char *ssid, const char *psk, char *err, int elen)
|
||
{
|
||
const char *wpa = "PATH=/bin:/sbin:/usr/bin:/usr/sbin wpa_cli -i wlan0";
|
||
char cmd[256];
|
||
snprintf(cmd, sizeof cmd, "%s add_network 2>/dev/null", wpa);
|
||
char *o = popen_read_tmo(1200, cmd);
|
||
int id = o ? atoi(o) : -1;
|
||
if (o) free(o);
|
||
if (id < 0) { if (err) snprintf(err, elen, "add_network 无 id"); return MEOW_ERR_INVAL; }
|
||
{
|
||
char *r;
|
||
snprintf(cmd, sizeof cmd, "%s set_network %d ssid '\"%s\"' 2>/dev/null", wpa, id, ssid);
|
||
if ((r = popen_read_tmo(1200, cmd))) free(r);
|
||
if (psk && psk[0]) {
|
||
snprintf(cmd, sizeof cmd, "%s set_network %d psk '\"%s\"' 2>/dev/null", wpa, id, psk);
|
||
if ((r = popen_read_tmo(1200, cmd))) free(r);
|
||
} else {
|
||
snprintf(cmd, sizeof cmd, "%s set_network %d key_mgmt NONE 2>/dev/null", wpa, id);
|
||
if ((r = popen_read_tmo(1200, cmd))) free(r);
|
||
}
|
||
snprintf(cmd, sizeof cmd, "%s enable_network %d 2>/dev/null", wpa, id);
|
||
if ((r = popen_read_tmo(1200, cmd))) free(r);
|
||
snprintf(cmd, sizeof cmd, "%s select_network %d 2>/dev/null", wpa, id);
|
||
if ((r = popen_read_tmo(1200, cmd))) free(r);
|
||
}
|
||
if (err) snprintf(err, elen, "connect: ssid=%s network_id=%d", ssid, id);
|
||
logmsg("netmgr_ipc: [direct-fallback] wifi connect ssid=%s id=%d", ssid, id);
|
||
return MEOW_OK;
|
||
}
|
||
|
||
int nm_wifi_connect(const char *ssid, const char *psk, char *err, int elen)
|
||
{
|
||
if (!ssid || !ssid[0]) { if (err) snprintf(err, elen, "ssid 缺失"); return MEOW_ERR_INVAL; }
|
||
char arg[132];
|
||
snprintf(arg, sizeof arg, "%s,%s", ssid, psk ? psk : ""); /* connect 带空 psk → netmgrd 复用既有网络 */
|
||
char text[192];
|
||
int rc = netmgr_wifi("connect", arg, text, sizeof text);
|
||
if (rc == NETMGR_UNREACH) return nm_direct_connect(ssid, psk, err, elen);
|
||
if (err && elen > 0) snprintf(err, elen, "%s", text);
|
||
return rc;
|
||
}
|
||
|
||
int nm_wifi_disconnect(char *err, int elen)
|
||
{
|
||
char text[128];
|
||
int rc = netmgr_wifi("disconnect", NULL, text, sizeof text);
|
||
if (rc == NETMGR_UNREACH) {
|
||
char cmd[160];
|
||
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin wpa_cli -i wlan0 disconnect 2>/dev/null");
|
||
popen_read_tmo(1200, cmd);
|
||
if (err) snprintf(err, elen, "已下发");
|
||
return MEOW_OK;
|
||
}
|
||
if (err && elen > 0) snprintf(err, elen, "%s", text);
|
||
return rc;
|
||
}
|
||
|
||
int nm_wifi_toggle(int on)
|
||
{
|
||
char text[128];
|
||
int rc = netmgr_wifi(on ? "on" : "off", NULL, text, sizeof text);
|
||
if (rc == NETMGR_UNREACH) { s_net_toggle_wifi(on); return MEOW_OK; } /* 直连回退 */
|
||
return rc;
|
||
}
|
||
|
||
|
||
/* ================= BT(IPC 优先:全量子命令权威收口 P1;不可达回退直连)================= */
|
||
|
||
static int nm_parse_devices(const char *txt, meow_net_t *out, int max); /* 前向声明 */
|
||
|
||
int nm_bt_powered(void)
|
||
{
|
||
char text[128];
|
||
int rc = netmgr_bt("status", NULL, text, sizeof text);
|
||
if (rc == NETMGR_UNREACH) return hci_powered();
|
||
if (rc != MEOW_OK) return 0;
|
||
char v[8];
|
||
nm_val(text, "powered=", v, sizeof v, 0);
|
||
return atoi(v);
|
||
}
|
||
|
||
int nm_bt_onoff(int on)
|
||
{
|
||
char text[128];
|
||
int rc = netmgr_bt(on ? "on" : "off", NULL, text, sizeof text);
|
||
if (rc == NETMGR_UNREACH) { s_net_toggle_bt(on); return MEOW_OK; }
|
||
return rc;
|
||
}
|
||
|
||
int nm_bt_status(int *powered, int *paired_n, char *addr, int alen)
|
||
{
|
||
char text[192];
|
||
int rc = netmgr_bt("status", NULL, text, sizeof text);
|
||
if (rc == NETMGR_UNREACH) { /* 直连回退:读内核/tool */
|
||
if (powered) *powered = hci_powered();
|
||
if (paired_n) *paired_n = -1;
|
||
if (addr && alen > 0) addr[0] = 0;
|
||
return rc;
|
||
}
|
||
if (rc == MEOW_OK) {
|
||
char v[8];
|
||
if (powered) { nm_val(text, "powered=", v, sizeof v, 0); *powered = atoi(v); }
|
||
if (paired_n) { nm_val(text, "paired=", v, sizeof v, 0); *paired_n = atoi(v); }
|
||
if (addr && alen > 0) nm_val(text, "address=", addr, alen, 0);
|
||
}
|
||
return rc;
|
||
}
|
||
|
||
int nm_bt_paired(meow_net_t *out, int max)
|
||
{
|
||
char text[MEOW_IPC_MAX - 32];
|
||
int rc = netmgr_bt("paired", NULL, text, sizeof text);
|
||
if (rc == NETMGR_UNREACH) return net_bt_paired(out, max);
|
||
if (rc != MEOW_OK) return 0;
|
||
return nm_parse_devices(text, out, max);
|
||
}
|
||
|
||
/* ---- P1 收口:连接/断开/配对/扫描/A2DP 全部迁 netmgrd(权威),不可达回退直连 ---- */
|
||
|
||
/* 通用 `Device <MAC> <Name>` 行解析(netmgrd bt {connected,paired,devices} 文本;launcher 只做客户端)。 */
|
||
static int nm_parse_devices(const char *txt, meow_net_t *out, int max)
|
||
{
|
||
int n = 0;
|
||
if (!txt || max <= 0) return 0;
|
||
if (max > S_NET_MAX) max = S_NET_MAX;
|
||
char *line = (char *)txt;
|
||
while (line && *line && n < max) {
|
||
char *eol = strchr(line, '\n'); if (eol) *eol = 0;
|
||
char *p = strstr(line, "Device ");
|
||
if (p) {
|
||
p += 7;
|
||
char *mac = p; while (*mac && *mac != ' ' && *mac != '\t') mac++;
|
||
char *nm2 = mac; while (*nm2 == ' ' || *nm2 == '\t') nm2++;
|
||
int ml = (int)(mac - p); if (ml > 47) ml = 47;
|
||
if (ml > 0 && ml < (int)sizeof out[n].mac) {
|
||
memcpy(out[n].mac, p, (size_t)ml); out[n].mac[ml] = 0;
|
||
}
|
||
snprintf(out[n].name, sizeof out[n].name, "%s", *nm2 ? nm2 : p);
|
||
out[n].id = -1; out[n].sec[0] = 0;
|
||
n++;
|
||
}
|
||
line = eol ? eol + 1 : (char *)0;
|
||
}
|
||
return n;
|
||
}
|
||
|
||
int nm_bt_connected(meow_net_t *out, int max)
|
||
{
|
||
char text[MEOW_IPC_MAX - 32];
|
||
int rc = netmgr_bt("connected", NULL, text, sizeof text);
|
||
if (rc == NETMGR_UNREACH) return net_bt_connected(out, max); /* 直连回退 */
|
||
if (rc != MEOW_OK) return 0;
|
||
return nm_parse_devices(text, out, max);
|
||
}
|
||
|
||
int nm_bt_devices(meow_net_t *out, int max)
|
||
{
|
||
char text[MEOW_IPC_MAX - 32];
|
||
int rc = netmgr_bt("devices", NULL, text, sizeof text);
|
||
if (rc == NETMGR_UNREACH) return net_bt_scan(out, max); /* 直连回退(读扫描缓存) */
|
||
if (rc != MEOW_OK) return 0;
|
||
return nm_parse_devices(text, out, max);
|
||
}
|
||
|
||
/* A2DP 端点解析:"A2DP <mac> codec=.. rate=.. channels=.. bits=.. acquired=.. role=.." */
|
||
int nm_bt_a2dp(bt_ep_t *eps, int max)
|
||
{
|
||
if (max <= 0) return 0;
|
||
char text[MEOW_IPC_MAX - 32];
|
||
int rc = netmgr_bt("a2dp", NULL, text, sizeof text);
|
||
if (rc == NETMGR_UNREACH || rc != MEOW_OK) return 0; /* A2DP 详情无直连回退(P2 bluealsa 才接) */
|
||
int n = 0;
|
||
char *line = text;
|
||
while (line && *line && n < max) {
|
||
char *eol = strchr(line, '\n'); if (eol) *eol = 0;
|
||
if (!strncmp(line, "A2DP ", 5)) {
|
||
char *p = line + 5;
|
||
bt_ep_t *e = &eps[n];
|
||
memset(e, 0, sizeof *e);
|
||
snprintf(e->role, sizeof e->role, "%s", "-");
|
||
e->acquired = 0;
|
||
char *mac = p; while (*mac && *mac != ' ' && *mac != '\t' && *mac != '\n') mac++;
|
||
int ml = (int)(mac - p); if (ml > 23) ml = 23;
|
||
if (ml > 0) { memcpy(e->mac, p, (size_t)ml); e->mac[ml] = 0; }
|
||
char *t = mac;
|
||
while (*t) {
|
||
while (*t == ' ' || *t == '\t') t++;
|
||
if (!strncmp(t, "codec=", 6)) { t += 6; nm_val_field(&t, e->codec, sizeof e->codec); }
|
||
else if (!strncmp(t, "rate=", 5)) { t += 5; nm_val_field(&t, e->rate, sizeof e->rate); }
|
||
else if (!strncmp(t, "channels=", 9)) { t += 9; nm_val_field(&t, e->chn, sizeof e->chn); }
|
||
else if (!strncmp(t, "bits=", 5)) { t += 5; nm_val_field(&t, e->bits, sizeof e->bits); }
|
||
else if (!strncmp(t, "acquired=", 9)) { t += 9; e->acquired = atoi(t); }
|
||
else if (!strncmp(t, "role=", 5)) { t += 5; nm_val_field(&t, e->role, sizeof e->role); }
|
||
while (*t && *t != ' ' && *t != '\t' && *t != '\n') t++;
|
||
}
|
||
n++;
|
||
}
|
||
line = eol ? eol + 1 : NULL;
|
||
}
|
||
return n;
|
||
}
|
||
|
||
/* 直连回退:bluetoothctl <sub> <mac>(带超时)。返回 MEOW_OK/MEOW_ERR_*。 */
|
||
static int bt_direct_cmd(const char *sub, const char *mac)
|
||
{
|
||
if (!mac || !mac[0]) return MEOW_ERR_INVAL;
|
||
char cmd[192];
|
||
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin bluetoothctl %s %s 2>/dev/null", sub, mac);
|
||
char *o = popen_read_tmo(8000, cmd);
|
||
int ok = o && (strstr(o, "successful") || strstr(o, "Connected") ||
|
||
strstr(o, "already") || strstr(o, "Paired"));
|
||
if (o) free(o);
|
||
return ok ? MEOW_OK : MEOW_ERR_NODEV;
|
||
}
|
||
|
||
int nm_bt_connect(const char *mac)
|
||
{
|
||
char text[128];
|
||
int rc = netmgr_bt("connect", mac, text, sizeof text);
|
||
if (rc == NETMGR_UNREACH) return bt_direct_cmd("connect", mac);
|
||
return rc;
|
||
}
|
||
int nm_bt_disconnect(const char *mac)
|
||
{
|
||
char text[128];
|
||
int rc = netmgr_bt("disconnect", mac, text, sizeof text);
|
||
if (rc == NETMGR_UNREACH) return bt_direct_cmd("disconnect", mac);
|
||
return rc;
|
||
}
|
||
int nm_bt_pair(const char *mac)
|
||
{
|
||
char text[128];
|
||
int rc = netmgr_bt("pair", mac, text, sizeof text);
|
||
if (rc == NETMGR_UNREACH) return bt_direct_cmd("pair", mac);
|
||
return rc;
|
||
}
|
||
int nm_bt_unpair(const char *mac)
|
||
{
|
||
char text[128];
|
||
int rc = netmgr_bt("unpair", mac, text, sizeof text);
|
||
if (rc == NETMGR_UNREACH) return bt_direct_cmd("remove", mac);
|
||
return rc;
|
||
}
|
||
void nm_bt_scan_on(void)
|
||
{
|
||
if (!netmgr_ok()) { net_bt_scan_trigger(); return; } /* 直连回退 */
|
||
netmgr_bt("scan", "on", NULL, 0);
|
||
}
|
||
void nm_bt_scan_off(void)
|
||
{
|
||
if (!netmgr_ok()) return;
|
||
netmgr_bt("scan", "off", NULL, 0);
|
||
}
|