Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/netmgr_ipc.c
T
AskaEth 7c7d891570 feat: M6.44+M6.45 收编 — 真机 0.8.45 现场保全(reader章节/key_router按键路由/WiFi连接修复/ncm bench+IME修复)
脏工作区现场收编:devdocs/local/m6_44_45_dirty_tracked.patch(46 个已跟踪文本文件,
+3090/-585, 已对 M6.43 基线干净适配) + m6_44_45_untracked_src.tar.gz 中 patch_v2/ 下
13 个新增源文件, 合成单个提交, 内容与容器工作树(R3ProII/patch_v2)逐字节一致。

- reader 章节: 新增 core/rdchapter.{h,c} — TXT 章节规范解析器, 扫描 512KB 码位对齐段文件
  逐段流式识别标题行(行首缩进≤4/显示宽≤40/行内标点≤1 三重约束防误判), 输出标题+全书字符
  偏移+段号/段内偏移+阿拉伯章号(中文数字归一), 首章前内容归"卷首"; book/reader/rdenc/
  rdlayout/settings 接入章节跳转与进度锚点, 附 chapter_smoke/turn_repro/build_turn_repro.sh
- key_router 按键路由: 新增 meow_launcherd/key_router.{h,c} — launcher-UI 页物理键统一路由
  (P1 仅音量键), 把读取边沿+防抖+长按 held+ramp 推进从 5 个会话收敛为进程内单开;
  各页每圈 key_router_pump() 取防抖后边沿, 动作(调音量/光标导航)仍由调用页决定,
  保留 run_menu 的 meow_mode 分支; App 前台 volume_page 武装时边沿抑制;
  main_menu/now_playing/notif_center/settings_page/subpage 接入, 附 test_key_router_vol/test_np_keys
- WiFi 连接修复: meow_netmgrd connect 有界等待 wpa_state=COMPLETED(普通 10s/隐藏网络 15s,
  env 可调)修设置页连接无响应; list_networks/scan_results 的 \xNN 转义还原始字节, 修中文 SSID
  永不命中致新建同名 open 记录顶影; launcher netmgr_ipc 配读超时; 附 test_wifi_connect(桩
  wpa_cli 驱动 static wifi_connect_ex), test_netmgrd.sh 补用例; wifi_page 同步
- ncm bench: 新增 bench/lyric_sim.c、bench/plpage_sim.c, vol_sync_sim 与 ncm core/app 同步
- SDK IME 修复: shift 大小写、K_ENTER 只提交不关、阻塞 IME 循环喂 on_tick 不被遮挡、
  IME 打开 ~200ms TAP 冷却窗吞 stray-TAP; 附 sdk/test_ime_fix.c
- 同期: meow_audiod 音量异步 ACK 线程(代际防 fd/槽复用竞态)、watchdog 音量收编(首选 audiod
  权威, settings.conf 覆盖路径)、panel 文件搜索(F1-④)与蓝牙只读详情(F1-③)、各类 build 脚本同步

范围红线: third_party/legado-E-main 原开发者未跟踪, 本次不入库(保留 devdocs/local tar 参考);
二进制不入库(后续重编); devdocs/ 不入库。59 文件全部在 patch_v2/ 下, 无 third_party/devdocs。
验证: git apply --check 干净; 59 文件 md5 与容器 patch_v2 逐一比对 59/59 一致(含
key_router.c/rdchapter.c/meow_ui_ime.c/test_wifi_connect.c 抽查); 可执行位保守
(check_lgpl_release.sh 100755, build_turn_repro.sh 100644 与容器一致)。
2026-11-04 20:02:28 +08:00

727 lines
29 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(测试/调用方共用) */
/* 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_*)。 */
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)
{
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); 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_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);
}
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);
}