diff --git a/patch_v2/meow_launcherd/bt_page.c b/patch_v2/meow_launcherd/bt_page.c index 48b2c0f..84a67c6 100644 --- a/patch_v2/meow_launcherd/bt_page.c +++ b/patch_v2/meow_launcherd/bt_page.c @@ -53,12 +53,12 @@ static int bt_top_sw(void *ctx, int *on) return 1; } -/* 顶栏开关切换:写设置 + hciconfig up/down + 触发扫描 + 同步状态栏 */ +/* 顶栏开关切换:写设置 + on/off(netmgr IPC,回退直连 hciconfig)+ 触发扫描 + 同步状态栏 */ static void bt_top_sw_set(void *ctx, int on) { (void)ctx; meow_settings_set_bool("network.bluetooth_enabled", on); - s_net_toggle_bt(on); + nm_bt_onoff(on); g_sys.bt_on = on; bt_role_refresh(1); paint_sb_right(); @@ -73,10 +73,12 @@ static int bt_rows(void *ctx, sub_row_t *rbuf, int max) { bt_ctx_t *c = ctx; int full = c->full; c->full = 0; /* 消费一次全量标记 */ - c->bt_on = hci_powered(); + c->bt_on = nm_bt_powered(); /* IPC status;回退直连 hci_powered() */ if (full || c->conn_n < 0) { /* 首次/动作后:全量重扫 conn/paired */ + /* conn 列表 netmgrd 无子命令(BT_CTL=status/on/off/paired),保持直连 bluetoothctl; + * paired 走 IPC(权威),回退直连。 */ c->conn_n = net_bt_connected(c->conn, S_NET_MAX); - c->paired_n = net_bt_paired(c->paired, S_NET_MAX); + c->paired_n = nm_bt_paired(c->paired, S_NET_MAX); if (c->conn_n < 0) c->conn_n = 0; if (c->paired_n < 0) c->paired_n = 0; } @@ -86,6 +88,11 @@ static int bt_rows(void *ctx, sub_row_t *rbuf, int max) net_bt_mode_str(c->mode, sizeof c->mode); int n = 0; + /* 降级占位:netmgrd 不可达 → 提示(直连仍在跑,保功能) */ + if (!netmgr_ok() && n < max) { + rbuf[n].kind = SUB_HDR; rbuf[n].label = "网络管理服务未运行(直连模式)"; + rbuf[n].sub = NULL; rbuf[n].on = 0; rbuf[n].act = 0; n++; + } /* 已连接设备 */ if (n < max) { rbuf[n].kind = SUB_HDR; rbuf[n].label = "已连接设备"; rbuf[n].sub = NULL; rbuf[n].on = 0; rbuf[n].act = 0; n++; } if (c->conn_n == 0) { diff --git a/patch_v2/meow_launcherd/build_launcher.sh b/patch_v2/meow_launcherd/build_launcher.sh index 5139b00..90b2f21 100755 --- a/patch_v2/meow_launcherd/build_launcher.sh +++ b/patch_v2/meow_launcherd/build_launcher.sh @@ -25,7 +25,7 @@ SYSROOT="$ROOT/.tools/mipsel-linux-muslsf-cross/mipsel-linux-muslsf" "$CC" -I"../sdk" -Os -static \ main_menu.c statusbar.c fb_compositor.c settings_page.c power_menu.c \ notif_center.c app_launcher.c audio.c backlight.c net.c about.c \ - subpage.c wifi_page.c bt_page.c now_playing.c \ + subpage.c wifi_page.c bt_page.c netmgr_ipc.c now_playing.c \ font_theme.c keys.c util.c meow_dep.c \ meow_touch.c meow_settings.c meow_notif.c meow_media.c meow_bgplay.c \ meow_ams_start.c \ diff --git a/patch_v2/meow_launcherd/meow_launcher_internal.h b/patch_v2/meow_launcherd/meow_launcher_internal.h index 0c18cbe..c937b0b 100644 --- a/patch_v2/meow_launcherd/meow_launcher_internal.h +++ b/patch_v2/meow_launcherd/meow_launcher_internal.h @@ -444,8 +444,10 @@ typedef struct { /* 已保存网络 / 扫描结果 / 蓝牙设备名称列表(用于下拉展示与连接动作)。 - * M2.76:追加 mac[24](AP BSSID / 蓝牙设备 MAC),供连接/配对/详情展示;无则空串。 */ -typedef struct { int id; char name[48]; char mac[24]; } meow_net_t; + * M2.76:追加 mac[24](AP BSSID / 蓝牙设备 MAC),供连接/配对/详情展示;无则空串。 + * M6.0:追加 sec[16](WiFi 扫描结果安全类型:Open/WPA-PSK/...;无则空=未知), + * 供「WiFi 新网络录入」判断是否需要弹 IME 输密码。 */ +typedef struct { int id; char name[48]; char mac[24]; char sec[16]; } meow_net_t; /* ================================================================== */ @@ -781,6 +783,33 @@ void s_net_refresh(void); void s_net_sync_from_sys(void); void s_net_toggle_bt(int on); void s_net_toggle_wifi(int on); + +/* ================= meow_netmgrd IPC 客户端(netmgr_ipc.c)================= + * M6.0 设置页迁移(NETMGR_DESIGN §12):wifi_page/bt_page 的查询/操作统一走 netmgrd + * IPC(socket /usr/data/meow_net.sock,env MEOW_NET_SOCK 覆盖)。netmgrd 是 WiFi/BT + * 操作权威;其不可达(NETMGR_UNREACH)时回退 net.c 直连 wpa_cli/hciconfig(双轨保功能)。 + * 迁移注释:此封装是「设置页客户端→权威 IPC」的唯一入口,netmgrd 不可达才走直连回退。 */ +#define NETMGR_UNREACH (-1000) /* 传输/握手不可达(区别于 netmgrd MEOW_ERR_* 业务码) */ +int netmgr_ok(void); /* 1=netmgrd 可达(结果约 1s 缓存,节流) */ +int netmgr_wifi(const char *cmd, const char *arg, char *text, int tlen); /* 返回 code / NETMGR_UNREACH */ +int netmgr_bt(const char *cmd, const char *arg, char *text, int tlen); + +/* WiFi(IPC 优先;网卡链路态 wlan_connected()/hci_powered() 仍读内核,属只读观测) */ +int nm_wifi_connected(void); +void nm_wifi_ssid(char *buf, int len); +int nm_wifi_info(char (*info)[48], int max); +int nm_wifi_saved(meow_net_t *out, int max); +int nm_wifi_scan(meow_net_t *out, int max); +void nm_wifi_scan_trigger(void); +int nm_wifi_connect(const char *ssid, const char *psk, char *err, int elen); /* MEOW_OK=成功 */ +int nm_wifi_disconnect(char *err, int elen); +int nm_wifi_toggle(int on); + +/* BT(IPC 优先:on/off/status/paired;连接/断开/配对/附近扫描 netmgrd 无子命令,仍直连) */ +int nm_bt_powered(void); +int nm_bt_onoff(int on); +int nm_bt_status(int *powered, int *paired_n, char *addr, int alen); +int nm_bt_paired(meow_net_t *out, int max); const meow_ui_theme_t *s_ui_theme(void); void sb_blit_icon(int icon_idx, int x, uint32_t fg, int ink_h); int scan_apps(struct app apps[MAX_APPS]); diff --git a/patch_v2/meow_launcherd/meow_launcherd b/patch_v2/meow_launcherd/meow_launcherd index 2863997..18628c6 100755 Binary files a/patch_v2/meow_launcherd/meow_launcherd and b/patch_v2/meow_launcherd/meow_launcherd differ diff --git a/patch_v2/meow_launcherd/net.c b/patch_v2/meow_launcherd/net.c index bfa28f1..3d7b1de 100644 --- a/patch_v2/meow_launcherd/net.c +++ b/patch_v2/meow_launcherd/net.c @@ -312,6 +312,22 @@ int net_wifi_scan(meow_net_t *out, int max) while (*se && *se != '\n' && *se != '\r') se++; while (se > ssid && (se[-1] == ' ' || se[-1] == '\t')) se--; int has = (se > ssid && *ssid); + /* M6.0:flags 首个方括号内容 = 安全类型([WPA-PSK-CCMP] 等);纯 [ESS]/[IBSS] = 开放。 + * 供「WiFi 新网络录入」判断是否需要弹 IME 输密码。 */ + char sec[16] = "Open"; + if (br) { + 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"); + } + } + } /* 去重:同一 BSSID 只留一条,优先具名 SSID */ int idx = -1; for (int j = 0; j < n; j++) if (!strcmp(seen_bssid[j], bssid)) { idx = j; break; } @@ -323,6 +339,7 @@ int net_wifi_scan(meow_net_t *out, int max) named[idx] = has; out[idx].id = -1; snprintf(out[idx].mac, sizeof out[idx].mac, "%s", bssid); /* 保留 BSSID 供详情展示 */ + snprintf(out[idx].sec, sizeof out[idx].sec, "%s", sec); if (has) { /* M2.41:扫描结果 SSID 也可能是 wpa_cli 的 \xNN 转义(中文),还原成真 UTF-8 */ char raw[96]; diff --git a/patch_v2/meow_launcherd/netmgr_ipc.c b/patch_v2/meow_launcherd/netmgr_ipc.c new file mode 100644 index 0000000..5184abf --- /dev/null +++ b/patch_v2/meow_launcherd/netmgr_ipc.c @@ -0,0 +1,505 @@ +/* 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; +} + +/* —— 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 优先:on/off/status/paired;其余子命令 netmgrd 无,直连)================= */ + +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; + int n = 0; + if (max > S_NET_MAX) max = S_NET_MAX; + if (text[0]) { + char *line = text; + 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 : NULL; + } + } + return n; +} diff --git a/patch_v2/meow_launcherd/test_netmgr_ipc.c b/patch_v2/meow_launcherd/test_netmgr_ipc.c new file mode 100644 index 0000000..c7a591a --- /dev/null +++ b/patch_v2/meow_launcherd/test_netmgr_ipc.c @@ -0,0 +1,260 @@ +/* test_netmgr_ipc.c — M6.0 宿主桩测试:设置页 netmgr IPC 客户端 / WiFi 新网络录入逻辑。 + * + * 假 socket 帧:本测试起一个 AF_UNIX 服务器线程,按 Meow IPC 帧(16B 头 + 负载)应答 + * MSG_HELLO→ACK、MSG_NET_WIFI_CTL/BT_CTL→*_RESULT(netmgrd 同构),让 launcher 侧 + * netmgr_ipc.c(netmgr_wifi / nm_wifi_* 系列 / netmgr_ok)与 wifi_page.c(wifi_rows)跑真实数据。 + * + * 覆盖(打桩帧): + * ① 扫描列表渲染:netmgrd 返回 scan_results → nm_wifi_scan / wifi_rows 解析出 SSID/mac/sec, + * 加密网络 sec=WPA-PSK、开放网络 sec=Open; + * ② 连接成功/失败两态:connect 子命令 MEOW_OK / MEOW_ERR_NODEV → nm_wifi_connect 返回值+错误文本; + * ③ 降级占位:MEOW_NET_SOCK 指向不存在路径 → netmgr_ok()==0、netmgr_wifi=NETMGR_UNREACH、 + * nm_wifi_scan 回退直连(桩返回 0 不崩),且 wifi_rows 渲染「网络管理服务未运行(直连模式)」占位。 + * + * 编译(链 netmgr_ipc.o;net.c 回退函数/UI 用桩替代,同 test_media.c 惯例): + * gcc -I../sdk -I. -Wall -Wextra -c netmgr_ipc.c -o netmgr_ipc.test.o + * gcc -I../sdk -I. -Wall -Wextra test_netmgr_ipc.c netmgr_ipc.test.o -lpthread -o test_netmgr_ipc + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "meow_launcher_internal.h" /* meow_net_t / S_NET_MAX / nm_* / sub_page_t etc. */ +#include "wifi_page.c" /* 把静态 wifi_ctx_t/wifi_rows 平移进本 TU(沿用既有模式) */ + +/* ---------- net.c 直连回退路径桩(netmgrd 不可达时走这里;宿主无 wpa_cli/bluetoothctl) ---------- */ +int wlan_connected(void) { return 0; } +int net_wifi_saved(meow_net_t *out, int max) { (void)out; (void)max; return 0; } +int net_wifi_scan(meow_net_t *out, int max) { (void)out; (void)max; return 0; } +void net_wifi_ssid(char *buf, int len) { (void)len; buf[0] = 0; } +int net_wifi_info(char (*info)[48], int max) { (void)info; (void)max; return 0; } +int hci_powered(void) { return 0; } +int net_bt_paired(meow_net_t *out, int max) { (void)out; (void)max; return 0; } +void s_net_toggle_wifi(int on) { (void)on; } +void s_net_toggle_bt(int on) { (void)on; } +void net_wifi_scan_trigger(void) {} +char *popen_read_tmo(int tmo_ms, const char *cmd) +{ (void)tmo_ms; (void)cmd; return strdup("0\n"); } /* 模拟 wpa_cli add_network 返回 id=0 */ + +/* ---------- launcher/UI/IME 桩(wifi_page.c 链接所需;测试不触碰这些路径) ---------- */ +static unsigned long long g_test_ms = 0; +int meow_settings_get_bool(const char *k, int *v) { (void)k; if (v) *v = 1; return 1; } +int meow_settings_set_bool(const char *k, int v) { (void)k; (void)v; return 0; } +int meow_settings_save(void) { return 0; } +void paint_sb_right(void) {} +void logmsg(const char *fmt, ...) { (void)fmt; } +unsigned long long now_ms(void) { return ++g_test_ms; } +void bt_role_refresh(int force) { (void)force; } +meow_sys_state g_sys; /* extern meow_sys_state g_sys */ +int s_dd_modal(const char *title, const char *item[S_NET_MAX], int n, int cur) +{ (void)title; (void)item; (void)n; (void)cur; return -1; } +int meow_subpage_run(sub_page_t *p) { (void)p; return 0; } +int meow_ui_text_input(meow_ime_ctx *ctx) { (void)ctx; return 0; } /* 取消:不进 IME 路径 */ + +/* ---------- 假 netmgrd 服务器 ---------- */ +typedef struct { + int lfd; + int fail_connect; /* 1=connect 返回 MEOW_ERR_NODEV */ + const char *scan_text; + const char *networks_text; + const char *status_text; + const char *bt_status_text; + const char *bt_paired_text; +} fake_net_t; + +static int rw_all(int fd, uint8_t *b, size_t n) +{ size_t o = 0; while (o < n) { ssize_t r = read(fd, b + o, n - o); if (r <= 0) return -1; o += (size_t)r; } return 0; } +static int wr_all(int fd, const uint8_t *b, size_t n) +{ size_t o = 0; while (o < n) { ssize_t r = write(fd, b + o, n - o); if (r <= 0) return -1; o += (size_t)r; } return 0; } + +static void fk_send(int fd, uint16_t type, uint32_t req, const void *pl, uint32_t len) +{ + 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 (wr_all(fd, hdr, sizeof hdr) < 0) return; + if (len && pl) wr_all(fd, pl, len); +} +static void fk_result(int fd, uint16_t type, uint32_t req, uint32_t code, const char *txt) +{ + m_net_cmd_result_t r; + memset(&r, 0, sizeof r); + r.code = code; + size_t pl = txt ? strlen(txt) : 0; + if (pl > sizeof r.text - 1) pl = sizeof r.text - 1; + r.text_len = (uint16_t)pl; + memcpy(r.text, txt, pl); + r.text[pl] = 0; + fk_send(fd, type, req, &r, (uint32_t)(8 + pl + 1)); +} + +static void fk_handle(int c, fake_net_t *f) +{ + for (;;) { + uint8_t hdr[MEOW_IPC_HDR_LEN]; + if (rw_all(c, hdr, sizeof hdr) < 0) return; + uint32_t magic, rlen, req; uint16_t ver, type; + memcpy(&magic, hdr, 4); memcpy(&ver, hdr + 4, 2); memcpy(&type, hdr + 6, 2); + memcpy(&req, hdr + 8, 4); memcpy(&rlen, hdr + 12, 4); + if (magic != MEOW_IPC_MAGIC || ver != MEOW_IPC_VERSION) return; + uint8_t pl[MEOW_IPC_MAX]; + if (rlen) { if (rlen > sizeof pl) rlen = sizeof pl; if (rw_all(c, pl, rlen) < 0) return; } + if (type == MSG_HELLO) { + m_ack_t a; memset(&a, 0, sizeof a); a.code = MEOW_OK; + fk_send(c, MSG_ACK, req, &a, sizeof a); + continue; + } + m_net_cmd_t q; memset(&q, 0, sizeof q); + if (rlen > sizeof q) rlen = sizeof q; + memcpy(&q, pl, rlen); + if (type == MSG_NET_WIFI_CTL) { + if (!strcmp(q.cmd, "scan")) fk_result(c, MSG_NET_WIFI_RESULT, req, MEOW_OK, f->scan_text); + else if (!strcmp(q.cmd, "networks")) fk_result(c, MSG_NET_WIFI_RESULT, req, MEOW_OK, f->networks_text); + else if (!strcmp(q.cmd, "status")) fk_result(c, MSG_NET_WIFI_RESULT, req, MEOW_OK, f->status_text); + else if (!strcmp(q.cmd, "connect")) { + char buf[200]; + if (f->fail_connect) fk_result(c, MSG_NET_WIFI_RESULT, req, MEOW_ERR_NODEV, + "connect: add_network 失败 (wpa_cli 不可用?)"); + else { snprintf(buf, sizeof buf, "connect: ssid=%s network_id=5 (已 enable+select)", q.arg); + fk_result(c, MSG_NET_WIFI_RESULT, req, MEOW_OK, buf); } + } + else if (!strcmp(q.cmd, "disconnect")) fk_result(c, MSG_NET_WIFI_RESULT, req, MEOW_OK, "wifi disconnect: 已下发"); + else if (!strcmp(q.cmd, "on") || !strcmp(q.cmd, "off")) fk_result(c, MSG_NET_WIFI_RESULT, req, MEOW_OK, "wifi on/off: rfkill wlan done"); + else fk_result(c, MSG_NET_WIFI_RESULT, req, MEOW_ERR_INVAL, "wifi: 未知子命令"); + continue; + } + if (type == MSG_NET_BT_CTL) { + if (!strcmp(q.cmd, "status")) fk_result(c, MSG_NET_BT_RESULT, req, MEOW_OK, f->bt_status_text); + else if (!strcmp(q.cmd, "paired")) fk_result(c, MSG_NET_BT_RESULT, req, MEOW_OK, f->bt_paired_text); + else if (!strcmp(q.cmd, "on") || !strcmp(q.cmd, "off")) fk_result(c, MSG_NET_BT_RESULT, req, MEOW_OK, "bt on/off: hci0 done"); + else fk_result(c, MSG_NET_BT_RESULT, req, MEOW_ERR_INVAL, "bt: 未知子命令"); + continue; + } + return; /* 未知帧:断开 */ + } +} + +static void *fk_server(void *arg) +{ + fake_net_t *f = arg; + for (;;) { + int c = accept(f->lfd, NULL, NULL); + if (c < 0) return NULL; + fk_handle(c, f); + close(c); + } +} + +static int start_server(fake_net_t *f, const char *path) +{ + f->lfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (f->lfd < 0) return -1; + struct sockaddr_un a; + memset(&a, 0, sizeof a); + a.sun_family = AF_UNIX; + strncpy(a.sun_path, path, sizeof a.sun_path - 1); + unlink(path); + if (bind(f->lfd, (struct sockaddr *)&a, sizeof a) < 0) return -2; + if (listen(f->lfd, 5) < 0) return -3; + pthread_t t; + return pthread_create(&t, NULL, fk_server, f) == 0 ? 0 : -4; +} + +static int row_has_label(const sub_row_t *rows, int n, const char *needle) +{ for (int i = 0; i < n; i++) if (rows[i].label && strstr(rows[i].label, needle)) return 1; return 0; } + +int main(void) +{ + char sock[110]; + snprintf(sock, sizeof sock, "/tmp/meow_net_test_%d.sock", (int)getpid()); + + fake_net_t f; + memset(&f, 0, sizeof f); + f.scan_text = + "bssid / frequency / signal level / flags / ssid\n" + "00:11:22:33:44:01 2412 -45 [WPA-PSK-CCMP][ESS] Home2G\n" + "00:11:22:33:44:02 2417 -60 [WPA2-PSK][ESS] Office\n" + "00:11:22:33:44:03 2442 -70 [ESS] FreeWiFi\n"; + f.networks_text = + "network id / ssid / bssid / flags\n" + "0\tHome2G\t00:11:22:33:44:01\t[WPA-PSK-CCMP]\n"; + f.status_text = "wifi state=COMPLETED connected=1 ssid=Home2G ip=192.168.1.5 rssi=-45"; + f.bt_status_text = "bt available=1 powered=1 address=00:1A:7D:DA:71:13 paired=2"; + f.bt_paired_text = "Device 00:1A:7D:DA:71:13 Headphones\nDevice 00:1A:7D:DA:71:14 Speaker\n"; + + if (start_server(&f, sock) != 0) { fprintf(stderr, "fake server start fail\n"); return 1; } + setenv("MEOW_NET_SOCK", sock, 1); + + /* ---- ① 扫描列表渲染:nm_wifi_scan 解析(IPsec/开放安全类型) ---- */ + meow_net_t scan[S_NET_MAX]; + int sn = nm_wifi_scan(scan, S_NET_MAX); + assert(sn >= 2 && "scan: 至少解析 2 条(BSSID 去重后)"); + assert(!strcmp(scan[0].name, "Home2G") && "scan[0].name=Home2G"); + assert(!strcmp(scan[0].sec, "WPA-PSK-CCMP") && "scan[0].sec 加密"); + assert(!strcmp(scan[1].sec, "WPA2-PSK") && "scan[1].sec 加密"); + /* FreeWiFi 无 WPA → Open */ + int open_idx = -1; + for (int i = 0; i < sn; i++) if (!strcmp(scan[i].name, "FreeWiFi")) open_idx = i; + assert(open_idx >= 0 && "scan 含 FreeWiFi"); + assert(!strcmp(scan[open_idx].sec, "Open") && "开放网络 sec=Open"); + printf("[A] nm_wifi_scan: %d 条, Home2G/WPA-PSK-CCMP, FreeWiFi/Open PASS\n", sn); + + /* ---- wifi_rows:附近网络分组渲染(含 SSID/sec 摘要行) ---- */ + { + wifi_ctx_t c; memset(&c, 0, sizeof c); + sub_row_t rows[SUB_ROWS_MAX]; + int rn = wifi_rows(&c, rows, SUB_ROWS_MAX); + assert(row_has_label(rows, rn, "附近网络") && "rows 有「附近网络」分组"); + assert(row_has_label(rows, rn, "Home2G") && "rows 渲染扫描 SSID Home2G"); + assert(row_has_label(rows, rn, "FreeWiFi") && "rows 渲染开放网络 FreeWiFi"); + assert(row_has_label(rows, rn, "已保存网络") && "rows 有「已保存网络」分组"); + assert(!row_has_label(rows, rn, "网络管理服务未运行") && "netmgrd 在 → 无降级占位"); + printf("[B] wifi_rows 渲染: 附近/已保存/扫描SSID 均现, 无降级占位 PASS\n"); + } + + /* ---- ② 连接成功/失败两态 ---- */ + char err[192]; err[0] = 0; + f.fail_connect = 0; + int rc = nm_wifi_connect("Home2G", "pass1234", err, sizeof err); + assert(rc == MEOW_OK && "connect 成功返回 MEOW_OK"); + assert(strstr(err, "network_id=5") && "connect 成功带回 network_id"); + printf("[C] connect 成功: rc=%d err=%s PASS\n", rc, err); + + f.fail_connect = 1; + err[0] = 0; + rc = nm_wifi_connect("Home2G", "bad", err, sizeof err); + assert(rc == MEOW_ERR_NODEV && "connect 失败返回 MEOW_ERR_NODEV"); + assert(strstr(err, "add_network") && "connect 失败带回原因"); + printf("[D] connect 失败: rc=%d err=%s PASS\n", rc, err); + + /* ---- ③ 降级占位:MEOW_NET_SOCK 指向不存在路径 ---- + * 先发一次 netmgr_wifi(内部强制重连,失败 → 置可达缓存=0),再断言 netmgr_ok()。 */ + setenv("MEOW_NET_SOCK", "/tmp/meow_net_nonexistent_zz.sock", 1); + assert(netmgr_wifi("status", NULL, err, sizeof err) == NETMGR_UNREACH && "netmgr_wifi=NETMGR_UNREACH"); + assert(netmgr_ok() == 0 && "netmgr_ok()==0(服务不可达)"); + err[0] = 0; + int dr = nm_wifi_connect("Home2G", "x", err, sizeof err); + assert(dr == MEOW_OK && "不可达→回退直连(桩返回成功,保功能)"); + int nr = nm_wifi_scan(scan, S_NET_MAX); + assert(nr == 0 && "不可达→回退直连扫描(桩返回 0,不崩)"); + { + wifi_ctx_t c; memset(&c, 0, sizeof c); + sub_row_t rows[SUB_ROWS_MAX]; + int rn = wifi_rows(&c, rows, SUB_ROWS_MAX); + assert(row_has_label(rows, rn, "网络管理服务未运行(直连模式)") && "降级占位渲染"); + printf("[E] 降级: netmgr_ok=0, UNREACH, 回退不崩, 占位渲染 PASS\n"); + } + + printf("ALL PASS\n"); + unlink(sock); + return 0; +} diff --git a/patch_v2/meow_launcherd/wifi_page.c b/patch_v2/meow_launcherd/wifi_page.c index d2e5970..47573dd 100644 --- a/patch_v2/meow_launcherd/wifi_page.c +++ b/patch_v2/meow_launcherd/wifi_page.c @@ -1,14 +1,16 @@ -/* wifi_page.c — MeowOS 系统设置「WiFi 独立子页面」(安卓风,M2.76)。 +/* wifi_page.c — MeowOS 系统设置「WiFi 独立子页面」(安卓风,M2.76;M6.0 迁移 netmgr IPC)。 * * 页面(sub_page_t 脚手架 subpage.c 驱动): * - 顶栏:返回 + 标题「WiFi」 + 右上开关(network.wifi_enabled) - * - 当前连接:SSID 行,点开 → 详情(SSID/AP MAC/本机 MAC/AP IP/本机 IP/频道) - * - 已保存网络:每项点 → wpa_cli select_network 连接;当前连接的项标「已连接」 - * - 附近网络:扫描结果(按 BSSID 去重——修复"AP 的 MAC 被当成单独设备列出"; - * 中文 SSID 经 wpa_decode_ssid 还原 \xNN 原始字节 → UTF-8 显示)点开 → 详情(SSID/AP MAC) + * - 当前连接:SSID 行,点开 → 详情(SSID/本机IP/信号)+ [断开连接] + * - 已保存网络:每项点 → netmgr wifi connect(ssid,复用既有网络);当前连接的项标「已连接」 + * - 附近网络:扫描结果(按 BSSID 去重;中文 SSID 经 wpa_decode_ssid 还原 \xNN → UTF-8) + * 点开 →【WiFi 新网络录入】加密网络弹 SDK IME 输密码(PW 模式)→ netmgr connect → 结果反馈; + * 开放网络直连。 * - * 均复用 net.c 既有读取(net_wifi_saved / net_wifi_scan / net_wifi_info / net_wifi_ssid / - * wlan_connected),写入只走 meow_settings_*;本机 MAC 等详情读 wpa_cli status。 + * 【M6.0 迁移】全部查询/操作走 netmgr IPC(netmgr_ipc.c 的 nm_* 封装),netmgrd 是 WiFi 权威; + * netmgrd 不可达(NETMGR_UNREACH)→ 自动回退 net.c 直连 wpa_cli(双轨保功能),页面显示降级占位 + * 「网络管理服务未运行(直连模式)」。 */ #include "meow_launcher_internal.h" @@ -16,16 +18,16 @@ /* action code 判位(act 回调据 act 值定位网络) */ #define WF_ACT_CURR (-1) /* 当前连接(SUB_ROW,detail) */ #define WF_ACT_SAVED 0 /* 已保存网络 idx(SUB_ACT,connect) */ -#define WF_ACT_SCAN_BASE 1000 /* 附近网络 1000+idx(SUB_ROW,detail) */ +#define WF_ACT_SCAN_BASE 1000 /* 附近网络 1000+idx(SUB_ROW,连接) */ typedef struct { meow_net_t saved[S_NET_MAX]; meow_net_t scan[S_NET_MAX]; int saved_n, scan_n; char ssid[64]; - int connected; /* wlan_connected() */ + int connected; /* nm_wifi_connected()(IPC>直连回退 wlan_connected) */ int wifi_on; /* network.wifi_enabled */ - long last_scan_ms; /* 扫描触发节流(net_wifi_scan_trigger 间隔) */ + long last_scan_ms; /* 扫描触发节流 */ } wifi_ctx_t; @@ -40,20 +42,20 @@ static int wifi_top_sw(void *ctx, int *on) } -/* 顶栏开关切换:写设置 + 开/关网卡 + 触发重扫 + 同步状态栏 */ +/* 顶栏开关切换:写设置 + 开/关网卡(netmgr on/off,回退直连)+ 触发重扫 + 同步状态栏 */ static void wifi_top_sw_set(void *ctx, int on) { wifi_ctx_t *c = ctx; meow_settings_set_bool("network.wifi_enabled", on); - s_net_toggle_wifi(on); + nm_wifi_toggle(on); g_sys.wifi_on = on; paint_sb_right(); /* 状态栏 WiFi 图标随开关即时刷新 */ meow_settings_save(); - if (on) { net_wifi_scan_trigger(); c->last_scan_ms = now_ms(); } + if (on) { nm_wifi_scan_trigger(); c->last_scan_ms = now_ms(); } } -/* 触发一次 WiFi 扫描(节流:1.5s 内不重复 spawn wpa_cli scan) */ +/* 触发一次 WiFi 扫描(节流:1.5s 内不重复。IPC 路径 scan 子命令由 nm_wifi_scan 内含触发+读取) */ static void wifi_scan(void *ctx) { wifi_ctx_t *c = ctx; @@ -61,28 +63,32 @@ static void wifi_scan(void *ctx) long t = (long)now_ms(); if (c->last_scan_ms && t - c->last_scan_ms < 1500) return; c->last_scan_ms = t; - net_wifi_scan_trigger(); + nm_wifi_scan_trigger(); } -/* 填行:刷新数据后构建分组列表 */ +/* 填行:刷新数据后构建分组列表(netmgr IPC 优先;不可达回退直连 + 降级占位) */ static int wifi_rows(void *ctx, sub_row_t *rbuf, int max) { wifi_ctx_t *c = ctx; - /* 刷新数据(popen 读列表;节流触发扫描) */ - c->connected = wlan_connected(); + c->connected = nm_wifi_connected(); if (c->connected) { - net_wifi_ssid(c->ssid, sizeof c->ssid); + nm_wifi_ssid(c->ssid, sizeof c->ssid); wifi_scan(c); } else { c->ssid[0] = 0; } - c->saved_n = net_wifi_saved(c->saved, S_NET_MAX); - c->scan_n = net_wifi_scan(c->scan, S_NET_MAX); + c->saved_n = nm_wifi_saved(c->saved, S_NET_MAX); + c->scan_n = nm_wifi_scan(c->scan, S_NET_MAX); if (c->saved_n < 0) c->saved_n = 0; if (c->scan_n < 0) c->scan_n = 0; int n = 0; + /* 降级占位:netmgrd 不可达 → 提示(直连仍在跑,保功能) */ + if (!netmgr_ok() && n < max) { + rbuf[n].kind = SUB_HDR; rbuf[n].label = "网络管理服务未运行(直连模式)"; + rbuf[n].sub = NULL; rbuf[n].on = 0; rbuf[n].act = 0; n++; + } /* 当前连接 */ if (n < max) { rbuf[n].kind = SUB_HDR; rbuf[n].label = "当前连接"; rbuf[n].sub = NULL; rbuf[n].on = 0; rbuf[n].act = 0; n++; } if (n < max) { @@ -99,7 +105,6 @@ static int wifi_rows(void *ctx, sub_row_t *rbuf, int max) } else for (int i = 0; i < c->saved_n && n < max; i++) { rbuf[n].kind = SUB_ACT; rbuf[n].label = c->saved[i].name[0] ? c->saved[i].name : "<未命名>"; - /* 当前连接的 SSID 标记「已连接」;否则显示 BSSID(若有) */ rbuf[n].sub = (c->connected && c->ssid[0] && !strcmp(c->saved[i].name, c->ssid)) ? "已连接" : (c->saved[i].mac[0] ? c->saved[i].mac : ""); rbuf[n].on = 0; rbuf[n].act = WF_ACT_SAVED + i; @@ -112,7 +117,8 @@ static int wifi_rows(void *ctx, sub_row_t *rbuf, int max) } else for (int i = 0; i < c->scan_n && n < max; i++) { rbuf[n].kind = SUB_ROW; rbuf[n].label = c->scan[i].name[0] ? c->scan[i].name : "(隐藏网络)"; - rbuf[n].sub = c->scan[i].mac[0] ? c->scan[i].mac : ""; + rbuf[n].sub = c->scan[i].sec[0] && strcmp(c->scan[i].sec, "Open") ? c->scan[i].sec + : (c->scan[i].mac[0] ? c->scan[i].mac : ""); rbuf[n].on = 0; rbuf[n].act = WF_ACT_SCAN_BASE + i; n++; } @@ -120,62 +126,106 @@ static int wifi_rows(void *ctx, sub_row_t *rbuf, int max) } -/* 附近网络详情(只读):SSID / AP MAC / 提示 */ -static int wifi_nearby_detail_cb(wifi_ctx_t *c, int scan_idx) +/* SDK IME 输密码(PW 模式=ASCII 软键盘输入,现有输入法复用;回车提交/ESC或点背景取消)。 + * 返回 1=已输入;0=取消。 */ +static int wifi_ask_psk(char *psk, int maxlen) +{ + meow_ime_ctx ic; + memset(&ic, 0, sizeof ic); + ic.mode = MEOW_IME_ASCII; + ic.maxlen = maxlen; + ic.buf[0] = 0; + int res = meow_ui_text_input(&ic); /* 阻塞:提交后 ctx.buf 回填最终文本 */ + if (res && ic.buf[0]) { snprintf(psk, maxlen, "%s", ic.buf); return 1; } + return 0; +} + + +/* 附近网络点选 →【WiFi 新网络录入】连接流程。返回 1=重绘。 */ +static int wifi_connect_scan(wifi_ctx_t *c, int scan_idx) { if (scan_idx < 0 || scan_idx >= c->scan_n) return 0; - char line[S_NET_MAX][48]; - const char *txt[S_NET_MAX]; - int n = 0; - snprintf(line[n], sizeof line[0], "SSID: %s", c->scan[scan_idx].name[0] ? c->scan[scan_idx].name : "(隐藏网络)"); txt[n] = line[n]; n++; - if (c->scan[scan_idx].mac[0]) { snprintf(line[n], sizeof line[n], "AP MAC: %s", c->scan[scan_idx].mac); txt[n] = line[n]; n++; } - snprintf(line[n], sizeof line[n], "密码/连接由 HibyOS 处理"); txt[n] = line[n]; n++; - s_dd_modal(c->scan[scan_idx].name[0] ? c->scan[scan_idx].name : "附近网络", txt, n, -1); + meow_net_t *s = &c->scan[scan_idx]; + if (!s->name[0] || !strcmp(s->name, "(隐藏网络)")) { + const char *txt[1]; txt[0] = "隐藏网络无法直接连接"; + s_dd_modal("附近网络", txt, 1, -1); + return 1; + } + int open = (s->sec[0] == 0 || !strcmp(s->sec, "Open")); + char psk[96] = ""; + if (!open) { + if (!wifi_ask_psk(psk, sizeof psk)) return 1; /* 用户取消输密码 */ + } + char err[160]; err[0] = 0; + int rc = nm_wifi_connect(s->name, open ? NULL : psk, err, sizeof err); + char line[192]; + const char *txt[1]; txt[0] = line; + if (rc == MEOW_OK) { + snprintf(line, sizeof line, "连接成功:%s%s", s->name, + err[0] ? "" : "(已启用)"); + } else { + snprintf(line, sizeof line, "连接失败:%s", err[0] ? err : "netmgr 返回错误"); + } + logmsg("wifi_page: scan connect ssid=%s open=%d rc=%d err=%s", s->name, open, rc, err); + /* 连接结果反馈(成功后进已保存网络列表由 rows() 重扫刷新) */ + s_dd_modal(s->name, txt, 1, -1); + nm_wifi_scan_trigger(); /* 连上后重扫,已保存/当前连接尽量贴近最新 */ + c->last_scan_ms = now_ms(); return 1; } -/* 普通行(SUB_ROW)点击:当前连接详情 / 附近网络详情 */ +/* 普通行(SUB_ROW)点击:当前连接详情(含断开)/ 附近网络连接 */ static int wifi_detail(void *ctx, int idx, int act) { (void)idx; wifi_ctx_t *c = ctx; if (act == WF_ACT_CURR) { - /* 当前连接详情(wpa_cli status + 网关):SSID/AP MAC/本机 MAC/AP IP/本机 IP/频道 */ char info[S_NET_MAX][48]; const char *txt[S_NET_MAX]; - int n = net_wifi_info(info, S_NET_MAX); + int n = nm_wifi_info(info, S_NET_MAX); if (n == 0) { snprintf(info[0], sizeof info[0], "%s", c->connected ? "已连接(无详情)" : "未连接"); txt[0] = info[0]; n = 1; } else { for (int i = 0; i < n; i++) txt[i] = info[i]; } - s_dd_modal(c->ssid[0] ? c->ssid : "当前连接", txt, n, -1); + if (c->connected && n < S_NET_MAX) { snprintf(info[n], sizeof info[n], "断开连接"); txt[n] = info[n]; n++; } + int pick = s_dd_modal(c->ssid[0] ? c->ssid : "当前连接", txt, n, -1); + if (pick >= 0 && c->connected && pick == n - 1) { + char err[96]; err[0] = 0; + int rc = nm_wifi_disconnect(err, sizeof err); + const char *rtxt[1]; char rline[128]; + rtxt[0] = rline; + snprintf(rline, sizeof rline, "%s", rc == MEOW_OK ? "已断开" : (err[0] ? err : "断开失败")); + s_dd_modal("断开", rtxt, 1, -1); + c->last_scan_ms = now_ms(); + } return 1; } - if (act >= WF_ACT_SCAN_BASE) return wifi_nearby_detail_cb(c, act - WF_ACT_SCAN_BASE); + if (act >= WF_ACT_SCAN_BASE) return wifi_connect_scan(c, act - WF_ACT_SCAN_BASE); return 0; } -/* 动作行(SUB_ACT)点击:已保存网络 → 连接 */ +/* 动作行(SUB_ACT)点击:已保存网络 → netmgr connect(ssid;复用既有网络) */ static int wifi_act(void *ctx, int idx, int act) { (void)idx; wifi_ctx_t *c = ctx; - if (act >= WF_ACT_SCAN_BASE) return 0; /* 附近是 detail,不走这里 */ + if (act >= WF_ACT_SCAN_BASE) return 0; /* 附近是 SUB_ROW(detail),不走这里 */ int sidx = act - WF_ACT_SAVED; if (sidx < 0 || sidx >= c->saved_n) return 0; if (c->saved[sidx].id < 0) return 0; - char cmd[192]; - snprintf(cmd, sizeof cmd, - "PATH=/bin:/sbin:/usr/bin:/usr/sbin wpa_cli -i wlan0 select_network %d 2>/dev/null", - c->saved[sidx].id); - system(cmd); - net_wifi_scan_trigger(); + char err[160]; err[0] = 0; + int rc = nm_wifi_connect(c->saved[sidx].name, NULL, err, sizeof err); + char line[192]; const char *txt[1]; txt[0] = line; + if (rc == MEOW_OK) snprintf(line, sizeof line, "已连接:%s", c->saved[sidx].name); + else snprintf(line, sizeof line, "连接失败:%s", err[0] ? err : "netmgr 返回错误"); + logmsg("wifi_page: saved connect ssid=%s rc=%d err=%s", c->saved[sidx].name, rc, err); + s_dd_modal(c->saved[sidx].name, txt, 1, -1); + nm_wifi_scan_trigger(); c->last_scan_ms = now_ms(); - logmsg("wifi_page: select_network %d", c->saved[sidx].id); return 1; /* 重绘 + 重填行 */ } @@ -196,7 +246,7 @@ int run_wifi_page(void) p.live = 0; /* WiFi 列表在动作/开关时重扫即可,避免驻留 popen 轮询 */ p.ctx = &c; /* 打开页面立即触发一次扫描,附近列表更贴近最新 */ - net_wifi_scan_trigger(); + nm_wifi_scan_trigger(); c.last_scan_ms = now_ms(); bt_role_refresh(0); /* 顺带刷新网络缓存(不影响本页) */ return meow_subpage_run(&p);