feat(launcher, NETMGR_DESIGN §12): 设置页 WiFi/蓝牙子页迁 netmgr IPC + WiFi 新网络录入 UI

- wifi_page/bt_page 查询/操作改走 netmgrd IPC(netmgr_ipc.c 客户端:
  socket /usr/data/meow_net.sock,HELLO 鉴权 → WIFI_CTL/BT_CTL → RESULT)。
- WiFi 新网络录入:扫描结果点选 →(加密网络)SDK IME 输密码 → connect →
  结果反馈 → 刷新已存网络;开放网络直连;当前连接详情含断开。
- 双轨过渡:netmgrd 不可达(NETMGR_UNREACH)回退 net.c 直连 wpa_cli/hciconfig
  (保功能),页面显示「网络管理服务未运行(直连模式)」降级占位(不崩)。
- BT 仅迁 on/off/status/paired(netmgrd BT_CTL 无连接/配对子命令,其余直连)。
- net.c net_wifi_scan 补 sec 字段(wifi_page.c 补 meow_net_t.sec)。
- 测试:+test_netmgr_ipc.c(假 socket 帧:扫描渲染/连接成功失败两态/降级占位)。
This commit is contained in:
MeowOS committed 2026-09-01 22:09:31 +08:00
1 parent cd4e2c247f
commit 3b6a73ac10
8 files changed
+922 -54

No files matched your search

+11 -4
View File
@@ -53,12 +53,12 @@ static int bt_top_sw(void *ctx, int *on)
return 1; return 1;
} }
/* 顶栏开关切换:写设置 + hciconfig up/down + 触发扫描 + 同步状态栏 */ /* 顶栏开关切换:写设置 + on/off(netmgr IPC,回退直连 hciconfig)+ 触发扫描 + 同步状态栏 */
static void bt_top_sw_set(void *ctx, int on) static void bt_top_sw_set(void *ctx, int on)
{ {
(void)ctx; (void)ctx;
meow_settings_set_bool("network.bluetooth_enabled", on); meow_settings_set_bool("network.bluetooth_enabled", on);
s_net_toggle_bt(on); nm_bt_onoff(on);
g_sys.bt_on = on; g_sys.bt_on = on;
bt_role_refresh(1); bt_role_refresh(1);
paint_sb_right(); paint_sb_right();
@@ -73,10 +73,12 @@ static int bt_rows(void *ctx, sub_row_t *rbuf, int max)
{ {
bt_ctx_t *c = ctx; bt_ctx_t *c = ctx;
int full = c->full; c->full = 0; /* 消费一次全量标记 */ 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 */ 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->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->conn_n < 0) c->conn_n = 0;
if (c->paired_n < 0) c->paired_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); net_bt_mode_str(c->mode, sizeof c->mode);
int 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) { 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) { if (c->conn_n == 0) {
+1 -1
View File
@@ -25,7 +25,7 @@ SYSROOT="$ROOT/.tools/mipsel-linux-muslsf-cross/mipsel-linux-muslsf"
"$CC" -I"../sdk" -Os -static \ "$CC" -I"../sdk" -Os -static \
main_menu.c statusbar.c fb_compositor.c settings_page.c power_menu.c \ 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 \ 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 \ 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_touch.c meow_settings.c meow_notif.c meow_media.c meow_bgplay.c \
meow_ams_start.c \ meow_ams_start.c \
@@ -444,8 +444,10 @@ typedef struct {
/* 已保存网络 / 扫描结果 / 蓝牙设备名称列表(用于下拉展示与连接动作)。 /* 已保存网络 / 扫描结果 / 蓝牙设备名称列表(用于下拉展示与连接动作)。
* M2.76:追加 mac[24](AP BSSID / 蓝牙设备 MAC),供连接/配对/详情展示;无则空串。 */ * M2.76:追加 mac[24](AP BSSID / 蓝牙设备 MAC),供连接/配对/详情展示;无则空串。
typedef struct { int id; char name[48]; char mac[24]; } meow_net_t; * 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_sync_from_sys(void);
void s_net_toggle_bt(int on); void s_net_toggle_bt(int on);
void s_net_toggle_wifi(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); const meow_ui_theme_t *s_ui_theme(void);
void sb_blit_icon(int icon_idx, int x, uint32_t fg, int ink_h); void sb_blit_icon(int icon_idx, int x, uint32_t fg, int ink_h);
int scan_apps(struct app apps[MAX_APPS]); int scan_apps(struct app apps[MAX_APPS]);
Binary file not shown.
+17
View File
@@ -312,6 +312,22 @@ int net_wifi_scan(meow_net_t *out, int max)
while (*se && *se != '\n' && *se != '\r') se++; while (*se && *se != '\n' && *se != '\r') se++;
while (se > ssid && (se[-1] == ' ' || se[-1] == '\t')) se--; while (se > ssid && (se[-1] == ' ' || se[-1] == '\t')) se--;
int has = (se > ssid && *ssid); 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 */ /* 去重:同一 BSSID 只留一条,优先具名 SSID */
int idx = -1; int idx = -1;
for (int j = 0; j < n; j++) if (!strcmp(seen_bssid[j], bssid)) { idx = j; break; } 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; named[idx] = has;
out[idx].id = -1; out[idx].id = -1;
snprintf(out[idx].mac, sizeof out[idx].mac, "%s", bssid); /* 保留 BSSID 供详情展示 */ snprintf(out[idx].mac, sizeof out[idx].mac, "%s", bssid); /* 保留 BSSID 供详情展示 */
snprintf(out[idx].sec, sizeof out[idx].sec, "%s", sec);
if (has) { if (has) {
/* M2.41:扫描结果 SSID 也可能是 wpa_cli 的 \xNN 转义(中文),还原成真 UTF-8 */ /* M2.41:扫描结果 SSID 也可能是 wpa_cli 的 \xNN 转义(中文),还原成真 UTF-8 */
char raw[96]; char raw[96];
+505
View File
@@ -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;
}
+260
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <assert.h>
#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;
}
+97 -47
View File
@@ -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 驱动): * 页面(sub_page_t 脚手架 subpage.c 驱动):
* - 顶栏:返回 + 标题「WiFi」 + 右上开关(network.wifi_enabled) * - 顶栏:返回 + 标题「WiFi」 + 右上开关(network.wifi_enabled)
* - 当前连接:SSID 行,点开 → 详情(SSID/AP MAC/本机 MAC/AP IP/本机 IP/频道) * - 当前连接:SSID 行,点开 → 详情(SSID/本机IP/信号)+ [断开连接]
* - 已保存网络:每项点 → wpa_cli select_network 连接;当前连接的项标「已连接」 * - 已保存网络:每项点 → netmgr wifi connect(ssid,复用既有网络);当前连接的项标「已连接」
* - 附近网络:扫描结果(按 BSSID 去重——修复"AP 的 MAC 被当成单独设备列出"; * - 附近网络:扫描结果(按 BSSID 去重;中文 SSID 经 wpa_decode_ssid 还原 \xNN → UTF-8)
* 中文 SSID 经 wpa_decode_ssid 还原 \xNN 原始字节 → UTF-8 显示)点开 → 详情(SSID/AP MAC) * 点开 →【WiFi 新网络录入】加密网络弹 SDK IME 输密码(PW 模式)→ netmgr connect → 结果反馈;
* 开放网络直连。
* *
* 均复用 net.c 既有读取(net_wifi_saved / net_wifi_scan / net_wifi_info / net_wifi_ssid / * 【M6.0 迁移】全部查询/操作走 netmgr IPC(netmgr_ipc.c 的 nm_* 封装),netmgrd 是 WiFi 权威;
* wlan_connected),写入只走 meow_settings_*;本机 MAC 等详情读 wpa_cli status。 * netmgrd 不可达(NETMGR_UNREACH)→ 自动回退 net.c 直连 wpa_cli(双轨保功能),页面显示降级占位
* 「网络管理服务未运行(直连模式)」。
*/ */
#include "meow_launcher_internal.h" #include "meow_launcher_internal.h"
@@ -16,16 +18,16 @@
/* action code 判位(act 回调据 act 值定位网络) */ /* action code 判位(act 回调据 act 值定位网络) */
#define WF_ACT_CURR (-1) /* 当前连接(SUB_ROW,detail) */ #define WF_ACT_CURR (-1) /* 当前连接(SUB_ROW,detail) */
#define WF_ACT_SAVED 0 /* 已保存网络 idx(SUB_ACT,connect) */ #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 { typedef struct {
meow_net_t saved[S_NET_MAX]; meow_net_t saved[S_NET_MAX];
meow_net_t scan[S_NET_MAX]; meow_net_t scan[S_NET_MAX];
int saved_n, scan_n; int saved_n, scan_n;
char ssid[64]; char ssid[64];
int connected; /* wlan_connected() */ int connected; /* nm_wifi_connected()(IPC>直连回退 wlan_connected) */
int wifi_on; /* network.wifi_enabled */ int wifi_on; /* network.wifi_enabled */
long last_scan_ms; /* 扫描触发节流(net_wifi_scan_trigger 间隔) */ long last_scan_ms; /* 扫描触发节流 */
} wifi_ctx_t; } 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) static void wifi_top_sw_set(void *ctx, int on)
{ {
wifi_ctx_t *c = ctx; wifi_ctx_t *c = ctx;
meow_settings_set_bool("network.wifi_enabled", on); meow_settings_set_bool("network.wifi_enabled", on);
s_net_toggle_wifi(on); nm_wifi_toggle(on);
g_sys.wifi_on = on; g_sys.wifi_on = on;
paint_sb_right(); /* 状态栏 WiFi 图标随开关即时刷新 */ paint_sb_right(); /* 状态栏 WiFi 图标随开关即时刷新 */
meow_settings_save(); 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) static void wifi_scan(void *ctx)
{ {
wifi_ctx_t *c = ctx; wifi_ctx_t *c = ctx;
@@ -61,28 +63,32 @@ static void wifi_scan(void *ctx)
long t = (long)now_ms(); long t = (long)now_ms();
if (c->last_scan_ms && t - c->last_scan_ms < 1500) return; if (c->last_scan_ms && t - c->last_scan_ms < 1500) return;
c->last_scan_ms = t; 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) static int wifi_rows(void *ctx, sub_row_t *rbuf, int max)
{ {
wifi_ctx_t *c = ctx; wifi_ctx_t *c = ctx;
/* 刷新数据(popen 读列表;节流触发扫描) */ c->connected = nm_wifi_connected();
c->connected = wlan_connected();
if (c->connected) { if (c->connected) {
net_wifi_ssid(c->ssid, sizeof c->ssid); nm_wifi_ssid(c->ssid, sizeof c->ssid);
wifi_scan(c); wifi_scan(c);
} else { } else {
c->ssid[0] = 0; c->ssid[0] = 0;
} }
c->saved_n = net_wifi_saved(c->saved, S_NET_MAX); c->saved_n = nm_wifi_saved(c->saved, S_NET_MAX);
c->scan_n = net_wifi_scan(c->scan, 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->saved_n < 0) c->saved_n = 0;
if (c->scan_n < 0) c->scan_n = 0; if (c->scan_n < 0) c->scan_n = 0;
int 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) { rbuf[n].kind = SUB_HDR; rbuf[n].label = "当前连接"; rbuf[n].sub = NULL; rbuf[n].on = 0; rbuf[n].act = 0; n++; }
if (n < max) { 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++) { } else for (int i = 0; i < c->saved_n && n < max; i++) {
rbuf[n].kind = SUB_ACT; rbuf[n].kind = SUB_ACT;
rbuf[n].label = c->saved[i].name[0] ? c->saved[i].name : "<未命名>"; 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)) ? "已连接" rbuf[n].sub = (c->connected && c->ssid[0] && !strcmp(c->saved[i].name, c->ssid)) ? "已连接"
: (c->saved[i].mac[0] ? c->saved[i].mac : ""); : (c->saved[i].mac[0] ? c->saved[i].mac : "");
rbuf[n].on = 0; rbuf[n].act = WF_ACT_SAVED + i; 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++) { } else for (int i = 0; i < c->scan_n && n < max; i++) {
rbuf[n].kind = SUB_ROW; rbuf[n].kind = SUB_ROW;
rbuf[n].label = c->scan[i].name[0] ? c->scan[i].name : "(隐藏网络)"; 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; rbuf[n].on = 0; rbuf[n].act = WF_ACT_SCAN_BASE + i;
n++; n++;
} }
@@ -120,62 +126,106 @@ static int wifi_rows(void *ctx, sub_row_t *rbuf, int max)
} }
/* 附近网络详情(只读):SSID / AP MAC / 提示 */ /* SDK IME 输密码(PW 模式=ASCII 软键盘输入,现有输入法复用;回车提交/ESC或点背景取消)。
static int wifi_nearby_detail_cb(wifi_ctx_t *c, int scan_idx) * 返回 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; if (scan_idx < 0 || scan_idx >= c->scan_n) return 0;
char line[S_NET_MAX][48]; meow_net_t *s = &c->scan[scan_idx];
const char *txt[S_NET_MAX]; if (!s->name[0] || !strcmp(s->name, "(隐藏网络)")) {
int n = 0; const char *txt[1]; txt[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++; s_dd_modal("附近网络", txt, 1, -1);
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++; } return 1;
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); 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; return 1;
} }
/* 普通行(SUB_ROW)点击:当前连接详情 / 附近网络详情 */ /* 普通行(SUB_ROW)点击:当前连接详情(含断开)/ 附近网络连接 */
static int wifi_detail(void *ctx, int idx, int act) static int wifi_detail(void *ctx, int idx, int act)
{ {
(void)idx; (void)idx;
wifi_ctx_t *c = ctx; wifi_ctx_t *c = ctx;
if (act == WF_ACT_CURR) { if (act == WF_ACT_CURR) {
/* 当前连接详情(wpa_cli status + 网关):SSID/AP MAC/本机 MAC/AP IP/本机 IP/频道 */
char info[S_NET_MAX][48]; char info[S_NET_MAX][48];
const char *txt[S_NET_MAX]; 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) { if (n == 0) {
snprintf(info[0], sizeof info[0], "%s", c->connected ? "已连接(无详情)" : "未连接"); snprintf(info[0], sizeof info[0], "%s", c->connected ? "已连接(无详情)" : "未连接");
txt[0] = info[0]; n = 1; txt[0] = info[0]; n = 1;
} else { } else {
for (int i = 0; i < n; i++) txt[i] = info[i]; 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; 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; return 0;
} }
/* 动作行(SUB_ACT)点击:已保存网络 → 连接 */ /* 动作行(SUB_ACT)点击:已保存网络 → netmgr connect(ssid;复用既有网络) */
static int wifi_act(void *ctx, int idx, int act) static int wifi_act(void *ctx, int idx, int act)
{ {
(void)idx; (void)idx;
wifi_ctx_t *c = ctx; 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; int sidx = act - WF_ACT_SAVED;
if (sidx < 0 || sidx >= c->saved_n) return 0; if (sidx < 0 || sidx >= c->saved_n) return 0;
if (c->saved[sidx].id < 0) return 0; if (c->saved[sidx].id < 0) return 0;
char cmd[192]; char err[160]; err[0] = 0;
snprintf(cmd, sizeof cmd, int rc = nm_wifi_connect(c->saved[sidx].name, NULL, err, sizeof err);
"PATH=/bin:/sbin:/usr/bin:/usr/sbin wpa_cli -i wlan0 select_network %d 2>/dev/null", char line[192]; const char *txt[1]; txt[0] = line;
c->saved[sidx].id); if (rc == MEOW_OK) snprintf(line, sizeof line, "已连接:%s", c->saved[sidx].name);
system(cmd); else snprintf(line, sizeof line, "连接失败:%s", err[0] ? err : "netmgr 返回错误");
net_wifi_scan_trigger(); 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(); c->last_scan_ms = now_ms();
logmsg("wifi_page: select_network %d", c->saved[sidx].id);
return 1; /* 重绘 + 重填行 */ return 1; /* 重绘 + 重填行 */
} }
@@ -196,7 +246,7 @@ int run_wifi_page(void)
p.live = 0; /* WiFi 列表在动作/开关时重扫即可,避免驻留 popen 轮询 */ p.live = 0; /* WiFi 列表在动作/开关时重扫即可,避免驻留 popen 轮询 */
p.ctx = &c; p.ctx = &c;
/* 打开页面立即触发一次扫描,附近列表更贴近最新 */ /* 打开页面立即触发一次扫描,附近列表更贴近最新 */
net_wifi_scan_trigger(); nm_wifi_scan_trigger();
c.last_scan_ms = now_ms(); c.last_scan_ms = now_ms();
bt_role_refresh(0); /* 顺带刷新网络缓存(不影响本页) */ bt_role_refresh(0); /* 顺带刷新网络缓存(不影响本页) */
return meow_subpage_run(&p); return meow_subpage_run(&p);