feat(launcher): 蓝牙/WiFi 独立子页面(安卓风)— 新增 subpage.c 统一脚手架(顶栏+分组列表+跟手滚动+左缘返回) + wifi_page.c(开关/当前连接详情/已保存/附近,BSSID去重+中文SSID) + bt_page.c(开关/已连接/已配对/附近扫描配对);SMENU 网络组收敛为 2 入口行 (agent-178)

launcher bd37c8b0→d10c0e8a; 改动文件 -Wall -Wextra 0 warning; 真机待验证: 蓝牙 pair/connect、开关联动、触摸几何
出入: 中文SSID/BSSID去重复用 net.c 既有实现; 模态复用 s_dd_modal(SDK 模态层未上收, 未改 SDK); s_net_open 旧分支留作死代码未删
This commit is contained in:
MeowOS committed 2026-08-31 03:41:51 +08:00
1 parent 366bf6bb5d
commit f2786552d5
10 files changed
+1122 -86

No files matched your search

+215
View File
@@ -0,0 +1,215 @@
/* 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;
}
/* 顶栏开关切换:写设置 + hciconfig up/down + 触发扫描 + 同步状态栏 */
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);
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 = hci_powered();
if (full || c->conn_n < 0) { /* 首次/动作后:全量重扫 conn/paired */
c->conn_n = net_bt_connected(c->conn, S_NET_MAX);
c->paired_n = net_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;
/* 已连接设备 */
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);
}