评审 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。
776 lines
32 KiB
C
776 lines
32 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(测试/调用方共用) */
|
||
|
||
/* 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);
|
||
}
|