Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/netmgr_ipc.c
T
AskaEth f49585dd62 fix(launcher): M47 r1 评审 P2 四项 — 未知态不写盘/不顶替 + deadline 从点击起算 + 开关弹窗后置清理
评审 r1 status=p2-4items / merge=fix-then-merge(reviewed e4eda3a)。逐条:

P2-1 未知态被写成 settings=0,把失真推到别的入口
- bt_page.c / wifi_page.c:`meow_settings_set_bool(..., disp == 1)` 加 `disp >= 0` 门。
  disp == -1(daemon 与直连都读不到)时**保留用户原意图不动**。该键被 notif_center /
  settings_page 当显示态读、被 app_launcher 用来决定自动拉起 WiFi,写 0 会把「不知道」
  污染成「确定已关」。
- test_bt_page.c 5d 期望同步改:未知 → settings 保持原意图 1(不再期望「回滚为 0」)。

P2-2 收尾后「探测未知」仍显示成确定态,第三态丢失
- mtog_calc:显示态**只认 probe**,probe < 0 直接返回 -1;last_real 不再顶替确定态
  (`(void)last_real`,仅由调用方用于弹窗文案)。超时/下发失败且探测未知 → 顶栏保留
  「关+?」、行内保留「状态未知」,与文件头契约一致。
- 弹窗文案按结论区分:「无法确认当前状态(上次已知:开启/关闭/未知)」vs
  「切换未生效:当前仍为开启/关闭」——不再出现「当前仍为开启」自相矛盾(该文案只在
  probe 已知且 != 意图时出现)。日志加 elapsed。

P2-3 deadline 起点错,注释上限是假的
- bt_page/wifi_page:`deadline` 从**点击时刻**起算(含下发耗时)。
- 注释改成如实上限:快路径 1~2s / ≈16 次探测;慢路径(on-off 读超时 1.5s + hciconfig
  0.3~2s,之后一次探测 status 1.5s + 直连 1.8s ≈3.3s)总阻塞最坏 ≈7s、预算内只够 1 次
  探测,此时只能确认「探测超时」而非「设备无响应」——文案已按此区分。异步化留给后续单。

P2-4 开关路径弹窗后缺 after_modal / 按键基线复位
- subpage.c:顶开关命中分支补上与行命中分支相同的后置清理(排空触摸 + 40ms 二次排空、
  触摸基线/cooldown、key_router_rebase、kd_*/kp_poll 基线、m_prev_*、after_modal=1)。
  s_dd_modal 是全屏压暗层,否则关闭后状态栏(0..27)残留压暗到下一次时钟重绘,电源/播放键
  也可能带进弹窗里的陈旧边沿。

顺带(评审观察,未改全局超时策略)
- netmgr_ipc.c:拆出 `nm_cmd_t(..., int *read_timeout)`;`nm_bt_power_state()` 走它,
  **读超时 = 状态未知且不把可达性缓存打成 down**(netmgrd 单线程忙时 `bt status` 自己
  跑 hciconfig 可能超 1.5s;旧行为会标 down 1s,令紧随的 bt_rows 闪出「服务未运行(直连
  模式)」并把后续探测切到 1.8s 直连)。WiFi/DNS 等其它调用保持旧语义(传 NULL)。

验证
- `bash patch_v2/meow_launcherd/build_launcher.sh test` → exit 0,**65 PASS / 0 FAIL / 0 warning**
  (矩阵 13 组,新增「下发失败·探测未知 → 未知不顶替」)。
- 交叉编译 0 warning;产物(临时副本,不入库)901020 B / md5 e21450ff6621a67fd854165413c58be1。
- `test_time32_abi.sh` → 4263=3 / 4403=0 ALL PASS;`pkg_install` 与 7 条切换中/未知态文案
  均在 strings 中(旧的两条「无法确认蓝牙/WiFi 状态」已合并为「无法确认当前状态(上次已知:%s)」)。
- 真机 --sysdump(只读,临时文件已删):exit 0,bt_on=1/wifi_on=1 与设备现状一致;
  Music du 前后 199164544;未替换 /usr/bin/meow_launcherd、未重启 launcherd。
- 交付形态不变:本 commit 只动 5 个源码文件;二进制与 scripts/build/fw_content.conf 对 main 零 diff。
2026-11-11 17:59:13 +08:00

776 lines
32 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.
/* 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(测试/调用方共用) */
/* M47:net.c 的直连三态探测(-1 未知 / 0 关 / 1 开)。声明在此而非头文件——头文件不在本单
* 改动范围;两处定义与用法同属 launcher 网络线。 */
int net_bt_power_probe(void);
int net_wifi_power_probe(void);
/* IPC 读超时(ms)。netmgrd 单线程 poll,connect/connecth 会阻塞 ~10s/~15s 有界等待
* wpa_state=COMPLETED(bug1),scan 内含 1.5s 触发等待(P3)——客户端 per-command 超时需
* 覆盖这些同步等待,其余读级子命令维持 1.5s 不被卡死。 */
#define NM_TMO_DFL 1500
#define NM_TMO_SCAN 5000
#define NM_TMO_CONNECT 12000
#define NM_TMO_CONNECTH 17000
/* 可达性缓存: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, int tmo_ms)
{
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, tmo_ms);
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, NM_TMO_DFL) < 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=不可达。
* tmo_ms:读超时(connect/connecth/scan 需覆盖 netmgrd 同步等待,见 NM_TMO_*)。
* read_timeout(可空):非空时,「帧收发失败/类型不符」这一支只回写 *read_timeout=1 且**不**
* 把可达性缓存打成 down——调用方据此区分「读超时」与「真不可达」。M47 的 BT 状态查询用它:
* netmgrd 单线程忙时(bt status 自己会跑 hciconfig)状态查询可能超过 1.5s,若据此标 down,
* 紧随的 bt_rows 会闪出「服务未运行(直连模式)」并把后续探测切到 1.8s 的 hciconfig
* (评审 r1 观察;global 超时常量不动,WiFi/DNS 路径行为不变)。 */
static int nm_cmd_t(uint16_t ctl_type, uint16_t res_type, const char *cmd, const char *arg,
char *text, int tlen, int tmo_ms, int *read_timeout)
{
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, tmo_ms) < 0 || rtype != res_type) {
close(fd);
if (read_timeout) *read_timeout = 1;
else 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;
}
static int nm_cmd(uint16_t ctl_type, uint16_t res_type, const char *cmd, const char *arg,
char *text, int tlen, int tmo_ms)
{
return nm_cmd_t(ctl_type, res_type, cmd, arg, text, tlen, tmo_ms, NULL);
}
int netmgr_wifi_tmo(const char *cmd, const char *arg, char *text, int tlen, int tmo_ms)
{ return nm_cmd(MSG_NET_WIFI_CTL, MSG_NET_WIFI_RESULT, cmd, arg, text, tlen, tmo_ms); }
int netmgr_wifi(const char *cmd, const char *arg, char *text, int tlen)
{ return netmgr_wifi_tmo(cmd, arg, text, tlen, NM_TMO_DFL); }
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, NM_TMO_DFL); }
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_tmo("scan", NULL, text, sizeof text, NM_TMO_SCAN); /* 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();
}
/* —— 直连回退:与 netmgrd wifi_connect/connecth 对齐(bug1/bug2/bug4)。
* 匹配既有(\xNN 还原同表示)→ 新建或(命中且带新 psk)更新凭据 → hidden 补 scan_ssid=1 →
* enable+select → 有界等待 wpa_state=COMPLETED → 成功 enable_network all + save_config(校验重试);
* 失败时本次新建的网络回收,报文明示原因。仅在 netmgrd 不可达时走此路径。 */
static const char NM_WPA_PREFIX[] = "PATH=/bin:/sbin:/usr/bin:/usr/sbin wpa_cli -i wlan0";
static int nm_direct_wpa(char *out, int cap, const char *args, int tmo)
{
char cmd[384];
snprintf(cmd, sizeof cmd, "%s %s 2>/dev/null", NM_WPA_PREFIX, args);
char *o = popen_read_tmo(tmo, cmd);
if (!o) { out[0] = 0; return -1; }
int l = (int)strlen(o);
if (l >= cap) l = cap - 1;
memcpy(out, o, (size_t)l); out[l] = 0;
free(o);
return l;
}
static int nm_direct_save_config(void)
{
char out[128];
if (nm_direct_wpa(out, sizeof out, "save_config", 1500) > 0 && strstr(out, "OK")) return 1;
nm_direct_wpa(out, sizeof out, "set update_config 1", 1500);
if (nm_direct_wpa(out, sizeof out, "save_config", 1500) > 0 && strstr(out, "OK")) return 1;
return 0;
}
static int nm_direct_connect_ex(const char *ssid, const char *psk, int hidden, char *err, int elen)
{
char out[1024]; char args[256];
int id = -1;
if (nm_direct_wpa(out, sizeof out, "list_networks", 1500) > 0) {
char *ln = strtok(out, "\n");
while (ln) {
char *tab0 = strchr(ln, '\t');
if (tab0) {
int this_id = atoi(ln);
char *ss = tab0 + 1;
char *tab1 = strchr(ss, '\t');
if (tab1) *tab1 = 0;
char clean[128], unesc[160], norm[128];
size_t cl = strlen(ss);
if (cl >= 2 && ss[0] == '"' && ss[cl - 1] == '"') {
memcpy(clean, ss + 1, cl - 2); clean[cl - 2] = 0;
} else {
snprintf(clean, sizeof clean, "%s", ss);
}
nm_decode_ssid(clean, unesc, sizeof unesc);
char *p = unesc; while (*p == ' ' || *p == '\t') p++;
snprintf(norm, sizeof norm, "%s", p);
size_t nl = strlen(norm);
while (nl > 0 && (norm[nl - 1] == ' ' || norm[nl - 1] == '\t')) norm[--nl] = 0;
if (strcmp(norm, ssid) == 0) { id = this_id; break; }
}
ln = strtok(NULL, "\n");
}
}
int created = 0;
if (id < 0) {
if (nm_direct_wpa(out, sizeof out, "add_network", 1500) <= 0) {
if (err) snprintf(err, elen, "add_network 无 id");
return MEOW_ERR_INVAL;
}
id = atoi(out);
if (id < 0) { if (err) snprintf(err, elen, "add_network 无 id"); return MEOW_ERR_INVAL; }
created = 1;
snprintf(args, sizeof args, "set_network %d ssid '\"%s\"'", id, ssid);
nm_direct_wpa(out, sizeof out, args, 1500);
if (psk && psk[0]) { snprintf(args, sizeof args, "set_network %d psk '\"%s\"'", id, psk); nm_direct_wpa(out, sizeof out, args, 1500); }
else { snprintf(args, sizeof args, "set_network %d key_mgmt NONE", id); nm_direct_wpa(out, sizeof out, args, 1500); }
} else if (psk && psk[0]) {
snprintf(args, sizeof args, "set_network %d psk '\"%s\"'", id, psk);
nm_direct_wpa(out, sizeof out, args, 1500);
}
if (hidden) { snprintf(args, sizeof args, "set_network %d scan_ssid 1", id); nm_direct_wpa(out, sizeof out, args, 1500); }
snprintf(args, sizeof args, "enable_network %d", id); nm_direct_wpa(out, sizeof out, args, 1500);
snprintf(args, sizeof args, "select_network %d", id); nm_direct_wpa(out, sizeof out, args, 1500);
int wait_ms = hidden ? 15000 : 10000;
int waited = 0, ok = 0; char ip[40] = "";
for (;;) {
char st[512];
if (nm_direct_wpa(st, sizeof st, "status", 1500) > 0) {
char *ws = strstr(st, "wpa_state=");
if (ws && !strncmp(ws + 10, "COMPLETED", 9)) {
ok = 1;
char *ib = strstr(st, "ip_address=");
if (ib) { ib += 11; char *ie = ib; while (*ie && *ie != '\n' && *ie != '\r') ie++;
int il = (int)(ie - ib); if (il >= (int)sizeof ip) il = (int)sizeof ip - 1;
memcpy(ip, ib, (size_t)il); ip[il] = 0; }
break;
}
}
if (waited >= wait_ms) break;
usleep(200000);
waited += 200;
}
if (!ok) {
if (created) { snprintf(args, sizeof args, "remove_network %d", id); nm_direct_wpa(out, sizeof out, args, 1500); nm_direct_save_config(); }
if (err) snprintf(err, elen, "连接失败:密码错误或信号不佳(超时未关联)");
logmsg("netmgr_ipc: [direct-fallback] wifi connect ssid=%s hidden=%d id=%d timeout", ssid, hidden, id);
return MEOW_ERR_NODEV;
}
nm_direct_wpa(out, sizeof out, "enable_network all", 1500);
int persisted = nm_direct_save_config();
if (err) snprintf(err, elen, "已连接:%s ip=%s%s", ssid, ip[0] ? ip : "-", persisted ? "" : "(记录未持久化)");
logmsg("netmgr_ipc: [direct-fallback] wifi connect ssid=%s hidden=%d id=%d ip=%s", ssid, hidden, id, ip[0] ? ip : "-");
return MEOW_OK;
}
int nm_wifi_connect_ex(const char *ssid, const char *psk, int hidden, 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 : ""); /* 带空 psk → netmgrd 复用既有网络 */
char text[192];
int rc = netmgr_wifi_tmo(hidden ? "connecth" : "connect", arg, text, sizeof text,
hidden ? NM_TMO_CONNECTH : NM_TMO_CONNECT);
if (rc == NETMGR_UNREACH) return nm_direct_connect_ex(ssid, psk, hidden, err, elen);
if (err && elen > 0) snprintf(err, elen, "%s", text);
return rc;
}
int nm_wifi_connect(const char *ssid, const char *psk, char *err, int elen)
{ return nm_wifi_connect_ex(ssid, psk, 0, err, elen); }
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);
}
/* ===== M47:设置页开关的「真实态」三态查询(-1 未知 / 0 关 / 1 开)=====
* 与 nm_bt_powered()/nm_wifi_connected() 的区别:那两个把「问不到」塌缩成 0(看起来像
* 「已关」),正是开关显示不可信的根因之一。M47 起设置页开关只用下面两个函数。 */
int nm_bt_power_state(void)
{
char text[192];
int read_timeout = 0;
/* 状态查询走专用入口:读超时 = 「状态未知」,且不把 netmgrd 标成 down(见 nm_cmd_t 注释)。 */
int rc = nm_cmd_t(MSG_NET_BT_CTL, MSG_NET_BT_RESULT, "status", NULL,
text, sizeof text, NM_TMO_DFL, &read_timeout);
if (read_timeout) return -1;
if (rc == NETMGR_UNREACH) return net_bt_power_probe(); /* 直连回退;失败=未知 */
if (rc != MEOW_OK) return -1; /* daemon 报错:不知道就是不知道 */
char v[8];
nm_val(text, "available=", v, sizeof v, 0);
if (v[0] && atoi(v) == 0) return 0; /* 无控制器 → 必为关 */
nm_val(text, "powered=", v, sizeof v, 0);
if (!v[0]) return -1; /* 无 powered 字段 → 未知 */
return atoi(v) ? 1 : 0;
}
/* WiFi 射频真实态(三态)。netmgrd 的 `wifi status` 只报 wpa_state/连接态,不报射频开关
* (见 meow_netmgrd.c wifi_status,字段仅 state/connected/ssid/ip/rssi),无法据此判断
* 「wifi 开关」;而 daemon 的 wifi on/off 恰好就是 rfkill wlan block/unblock,所以射频态
* 取本地 rfkill(回退 wlan0 IFF_UP)——与 daemon 改的是同一份内核状态,且探测失败=未知。 */
int nm_wifi_power_state(void)
{
return net_wifi_power_probe();
}
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);
}