Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/bt_page.c
T
MeowOS 3b6a73ac10 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 帧:扫描渲染/连接成功失败两态/降级占位)。
2026-09-01 22:09:31 +08:00

223 lines
9.5 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* bt_page.c — MeowOS 系统设置「蓝牙独立子页面」(安卓风,M2.76)。
*
* 页面(sub_page_t 脚手架 subpage.c 驱动):
* - 顶栏:返回 + 标题「蓝牙」 + 右上开关(network.bluetooth_enabled)
* - 已连接设备:tap → bluetoothctl disconnect(角色经 bt_role_refresh 判定)
* - 已配对设备:tap → bluetoothctl connect(已连者标「已连接」)
* - 附近设备:扫描发现(net_bt_scan,后台有界 bluetoothctl 写 /tmp 缓存,异步不阻塞)——
* 未配对者 tap → pair;已配对者 tap → connect。实机配对成功率/超时需真机验证。
*
* 与网络线共用 net.c(net_bt_connected/paired/scan/scan_trigger/hci_powered/bt_role)。
* 开关写 network.bluetooth_enabled + hciconfig;连接/断开/配对走 bluetoothctl(带超时)。
*/
#include "meow_launcher_internal.h"
#define BT_ACT_CONN_BASE 2000 /* 已连接 idx(tap→disconnect) */
#define BT_ACT_PAIRED_BASE 3000 /* 已配对 idx(tap→connect) */
#define BT_ACT_NEAR_BASE 4000 /* 附近 idx(tap→pair/connect) */
typedef struct {
meow_net_t conn[S_NET_MAX];
meow_net_t paired[S_NET_MAX];
meow_net_t near[S_NET_MAX];
int conn_n, paired_n, near_n;
int bt_on;
int full; /* 1=本次 rows() 全量重扫(conn/paired popen);0=仅附近(file)+角色(节流) */
char mode[48]; /* bt_role 中文描述(次行摘要) */
} bt_ctx_t;
/* 设备 MAC(net_bt_* 已填 mac) */
static int bt_is_conn(const bt_ctx_t *c, const char *mac)
{
if (!mac[0]) return 0;
for (int i = 0; i < c->conn_n; i++) if (!strcmp(c->conn[i].mac, mac)) return 1;
return 0;
}
static int bt_is_paired(const bt_ctx_t *c, const char *mac)
{
if (!mac[0]) return 0;
for (int i = 0; i < c->paired_n; i++) if (!strcmp(c->paired[i].mac, mac)) return 1;
return 0;
}
/* 顶栏开关当前值 */
static int bt_top_sw(void *ctx, int *on)
{
(void)ctx;
int v = 0;
if (meow_settings_get_bool("network.bluetooth_enabled", &v) != 0) v = 0;
*on = v;
return 1;
}
/* 顶栏开关切换:写设置 + 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);
nm_bt_onoff(on);
g_sys.bt_on = on;
bt_role_refresh(1);
paint_sb_right();
meow_settings_save();
if (on) net_bt_scan_trigger();
}
/* 填行:刷新数据后构建分组列表。
* conn/paired 走 bluetoothctl(慢);仅全量(full)时重扫,避开子页 live tick 反复 spawn。 */
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 = 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 = 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;
}
c->near_n = net_bt_scan(c->near, S_NET_MAX); /* 附近:读 /tmp 缓存(快,实测无 popen) */
if (c->near_n < 0) c->near_n = 0;
/* 角色描述:节流(≤5s)重探,读缓存,避免子页驻留期间反复 popen */
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) {
if (n < max) { rbuf[n].kind = SUB_ROW; rbuf[n].label = "无已连接设备"; rbuf[n].sub = ""; rbuf[n].on = 0; rbuf[n].act = -2; n++; }
} else for (int i = 0; i < c->conn_n && n < max; i++) {
rbuf[n].kind = SUB_ACT;
rbuf[n].label = c->conn[i].name[0] ? c->conn[i].name : c->conn[i].mac;
rbuf[n].sub = c->conn[i].mac;
rbuf[n].on = 1; rbuf[n].act = BT_ACT_CONN_BASE + i;
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->paired_n == 0) {
if (n < max) { rbuf[n].kind = SUB_ROW; rbuf[n].label = "无已配对设备"; rbuf[n].sub = ""; rbuf[n].on = 0; rbuf[n].act = -2; n++; }
} else for (int i = 0; i < c->paired_n && n < max; i++) {
rbuf[n].kind = SUB_ACT;
rbuf[n].label = c->paired[i].name[0] ? c->paired[i].name : c->paired[i].mac;
rbuf[n].sub = bt_is_conn(c, c->paired[i].mac) ? "已连接"
: (c->paired[i].mac[0] ? c->paired[i].mac : "");
rbuf[n].on = bt_is_conn(c, c->paired[i].mac);
rbuf[n].act = BT_ACT_PAIRED_BASE + i;
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->near_n == 0) {
if (n < max) { rbuf[n].kind = SUB_ROW; rbuf[n].label = "附近无设备(点顶部开关后自动扫描)"; rbuf[n].sub = ""; rbuf[n].on = 0; rbuf[n].act = -2; n++; }
} else for (int i = 0; i < c->near_n && n < max; i++) {
int is_paired = bt_is_paired(c, c->near[i].mac);
if (is_paired) continue; /* 已配对者归于「已配对设备」,附近只列未配对 */
rbuf[n].kind = SUB_ACT;
rbuf[n].label = c->near[i].name[0] ? c->near[i].name : c->near[i].mac;
rbuf[n].sub = c->near[i].mac;
rbuf[n].on = 0; rbuf[n].act = BT_ACT_NEAR_BASE + i;
n++;
}
return n;
}
/* 执行 bluetoothctl 命令(带超时)+ 结果弹窗反馈 */
static void bt_run(const char *title, const char *cmd)
{
char *o = popen_read_tmo(8000, cmd);
char line[48];
const char *txt[4]; txt[0] = line;
int ok = o && (strstr(o, "successful") || strstr(o, "Connected") ||
strstr(o, "already") || strstr(o, "Paired"));
snprintf(line, sizeof line, "%s", ok ? "操作成功" : "操作失败/超时");
if (o) free(o);
s_dd_modal(title, txt, 1, -1);
}
/* 动作行(SUB_ACT)点击:连接/断开/配对 */
static int bt_act(void *ctx, int idx, int act)
{
(void)idx;
bt_ctx_t *c = ctx;
char mac[24] = "";
/* 定位 MAC(按 act 段) */
if (act >= BT_ACT_NEAR_BASE) {
int i = act - BT_ACT_NEAR_BASE;
if (i < 0 || i >= c->near_n) return 0;
snprintf(mac, sizeof mac, "%s", c->near[i].mac);
} else if (act >= BT_ACT_PAIRED_BASE) {
int i = act - BT_ACT_PAIRED_BASE;
if (i < 0 || i >= c->paired_n) return 0;
snprintf(mac, sizeof mac, "%s", c->paired[i].mac);
} else if (act >= BT_ACT_CONN_BASE) {
int i = act - BT_ACT_CONN_BASE;
if (i < 0 || i >= c->conn_n) return 0;
snprintf(mac, sizeof mac, "%s", c->conn[i].mac);
}
if (!mac[0]) return 0;
if (act >= BT_ACT_NEAR_BASE) {
/* 附近未配对 → pair;已配对(理论上过滤了)→ connect */
char cmd[192];
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin bluetoothctl pair %s 2>/dev/null", mac);
bt_run("配对", cmd);
} else if (act >= BT_ACT_PAIRED_BASE) {
/* 已配对:未连 → connect;已连 → disconnect */
if (bt_is_conn(c, mac)) {
char cmd[192];
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin bluetoothctl disconnect %s 2>/dev/null", mac);
bt_run("断开", cmd);
} else {
char cmd[192];
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin bluetoothctl connect %s 2>/dev/null", mac);
bt_run("连接", cmd);
}
} else {
/* 已连接 → disconnect */
char cmd[192];
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin bluetoothctl disconnect %s 2>/dev/null", mac);
bt_run("断开", cmd);
}
bt_role_refresh(1);
net_bt_scan_trigger();
c->full = 1; /* 动作后:下一次 rows() 全量重扫 */
logmsg("bt_page: act act=%d mac=%s", act, mac);
return 1; /* 重绘 + 重填行 */
}
/* 运行蓝牙独立子页面(阻塞到返回) */
int run_bt_page(void)
{
static bt_ctx_t c;
memset(&c, 0, sizeof c);
sub_page_t p;
memset(&p, 0, sizeof p);
p.title = "蓝牙";
p.top_sw = bt_top_sw;
p.top_sw_set = bt_top_sw_set;
p.rows = bt_rows;
p.act = bt_act;
p.detail = NULL; /* 蓝牙动作都走 SUB_ACT(act),无 SUB_ROW 详情 */
p.live = 1; /* 附近扫描驻留更新(约 2s 重扫行;仅附近/角色,不反复 spawn) */
p.ctx = &c;
c.full = 1; /* 首行全量重扫 */
net_bt_scan_trigger(); /* 进页立即触发一次附近扫描(异步,不阻塞) */
bt_role_refresh(1);
return meow_subpage_run(&p);
}