/* 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); }