fix(launcher): M47 设置页蓝牙/WiFi 开关只认真实态 — 三态查询 + 待确认收敛 + 未知态呈现
用户原话:「开关都存在显示问题 我都不能判断它是不是真的开启还是关闭(包括 WiFi 页也是)」。
根因不是「没读到硬件态」,而是「读到/没读到都被拿去当用户意图的装饰」:
- bt_page.c:103 开关初值来自 settings 布尔;:112-114 下发后立刻 g_sys.bt_on = on(显示意图);
:129 c->bt_on = nm_bt_powered() 赋值后全文无人引用(M45 也独立发现)。
- wifi_page.c:40 读不到 settings 时默认 1(显示已开)。
- netmgrd 的 bt/wifi 查询本身把「问不到」塌缩成 0(nm_bt_powered/nm_wifi_connected)。
本单只解「状态可信」,不碰扫描/配对/音频(另有 M45/M46 单)。
1) 真实态三态(-1 未知 / 0 关 / 1 开)
- net.c 新增 net_bt_power_probe()(hciconfig hci0 UP/DOWN;无输出=未知)与
net_wifi_power_probe()(rfkill wlan soft/hard 汇总,回退 wlan0 IFF_UP;读不到=未知)。
- netmgr_ipc.c 新增 nm_bt_power_state()(daemon bt status 的 available/powered 权威,不可达
回退直连探测)与 nm_wifi_power_state()(netmgrd 的 wifi status 只有 wpa_state、不报射频态,
故取本地 rfkill——daemon 的 wifi on/off 改的就是同一份内核状态)。
- 两个子页的 top_sw/rows 只读这两个函数;settings 布尔降级为「意图持久化」,不再决定外观。
- 「读不到默认 1」删除:未知就是未知。
2) 点击 → 待确认 → 真实态定稿
- 纯函数 meow_toggle_{display,pending,rollback,error}(bt_page.c,无 I/O/UI):
只有 probe 确认才算收敛;probe 未知继续等,绝不因 last_real 恰好等于意图就宣布成功。
- 页面:落盘意图 → 下发 → 250ms 轮询(BT 上限 4s、WiFi 上限 5s;hciconfig 0.3~2s、
wifi_up.sh 起 supplicant/DHCP 更慢,故不用 20ms 级忙轮询)→ 真实态定稿;失败/超时把
settings 回滚到已确认真实态 + s_dd_modal 可见报错,不静默、不乐观。
- subpage.c:切换前先画「正在开启/关闭…(等待设备确认)」条(待确认态与稳定态可区分);
切换后回读 top_sw 定稿(取消原「点了就翻面」),live tick 也回读,外部改动驻留期间可见。
3) 第三态呈现(未知 ≠ 已关)
- SDK meow_switch 只有两态:未知时开关停「关」并在左侧画灰色「?」;行内另有
「蓝牙状态未知 / WiFi 开关状态未知」文字说明。选「?」是因为它不占用开关控件本身、
且「关+?」不会被读成「已关」。
4) 顺带修 net.c 三条开关失真(塔追加的 M45 盘点,同一病根)
- s_net_toggle_bt() 补 rfkill soft(与 netmgrd 的 bt on/off 同一语义;开:先 unblock 再 up;
关:先 down 再 block)。此前只发 hciconfig → 「BT 页关过再从头开」会被 rfkill 拦下。
- s_net_sync_from_sys() WiFi 判据从 wlan_connected()(已关联)改为射频态;BT 用三态,
读不到不写(旧写法把「网卡 up 未关联」写成 0,把未知写成已关)。
- rfkill 同类多节点(本机 2 wlan + 2 bluetooth)按「任一 block 即 block」读、全部写入。
测试(`bash patch_v2/meow_launcherd/build_launcher.sh test`,新增 test 入口)
- test_bt_page.c:P4 三态与未知占位、P5 收敛四例(达成/下发失败/超时/未知)、P6 收敛矩阵
12 组(真实开/关 × 下发成功/失败/超时 × probe 未知),全 PASS(64 条断言)。
- test_netmgr_ipc.c:新增 nm_bt_power_state 解析(powered=1/0、available=0、缺字段→-1)、
nm_wifi_power_state 透传、不可达回退=未知、wifi_rows 未知态占位;并修掉两处既有测试缺陷
(行内 UI 桩缺失导致本文件此前根本链不过;直连连接桩统一返回 "0\n" 导致等满 10s 必失败)。
- 交叉编译 0 warning(-Wall -Wextra,逐文件单独编 + 整链);launcherd 899388 B
md5 1c88661a9410ac1a4139304362281cdd,同 commit 同步 scripts/build/fw_content.conf 钉值。
真机(10.58.215.50,仅只读自检,未替换 /usr/bin/meow_launcherd、未刷机)
- 二进制放 /usr/data/mnt/sd_0/MeowOS/tmp/ 跑 --sysdump:exit 0,输出 wifi_on=1 / bt_seen=1
bt_on=0,与设备现状一致(rfkill 全 0 + wlan0 up + hci0 DOWN + netmgrd powered=0)。
- 卡上 du -s /usr/data/mnt/sd_0/Music 前后恒 199164544;临时文件已删;settings 未改。
- launcherd 在 squashfs 里 → 本改动需刷机生效。
已知不在本单:输出目标只写 settings(audiod 仅启动读一次)→ M45 finding / M46;
netmgrd 侧四处失真(未知当关、up 与 unblock 顺序、rfkill 只写首节点、wifi status 无射频字段)
已报 finding;设置主页/通知中心的开关显示仍是 settings(另立单)。
This commit is contained in:
1 parent
ebd64d07e1
commit
b20e3c43e9
10 files changed
+708
-69
No files matched your search
@@ -17,8 +17,14 @@
|
|||||||
*
|
*
|
||||||
* 【扫描节流(§七)】进页 nm_bt_scan_on、退页/超时 nm_bt_scan_off;7s 节流由 netmgrd 统一控。
|
* 【扫描节流(§七)】进页 nm_bt_scan_on、退页/超时 nm_bt_scan_off;7s 节流由 netmgrd 统一控。
|
||||||
*
|
*
|
||||||
* 与网络线共用 net.c(net_bt_* 直连回退 + bt_role 角色判定)。开关写 network.bluetooth_enabled +
|
* 【M47 开关可信化】顶栏开关的显示态只来自真实态查询(daemon `bt status` 的 powered,
|
||||||
* netmgrd on/off;连接/断开/配对经 netmgrd(netmgr_ipc.c nm_bt_* 封装)。
|
* 不可达时直连 hciconfig 探测),三态:-1 未知 / 0 关 / 1 开;探测失败=未知(画「?」,不当已关)。
|
||||||
|
* settings 的 network.bluetooth_enabled 降级为「意图持久化」,不再决定开关外观。点击后先出示
|
||||||
|
* 「正在开启/关闭…(等待设备确认)」,下发命令 → 有界轮询真实态收敛 → 用真实态定稿;失败/超时
|
||||||
|
* 回滚并弹窗告知(不许乐观显示成功)。
|
||||||
|
*
|
||||||
|
* 与网络线共用 net.c(net_bt_* 直连回退 + bt_role 角色判定)。连接/断开/配对经 netmgrd
|
||||||
|
* (netmgr_ipc.c nm_bt_* 封装)。
|
||||||
*/
|
*/
|
||||||
#include "meow_launcher_internal.h"
|
#include "meow_launcher_internal.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -42,18 +48,35 @@ typedef struct {
|
|||||||
meow_net_t near[S_NET_MAX];
|
meow_net_t near[S_NET_MAX];
|
||||||
bt_ep_t a2dp[S_NET_MAX];
|
bt_ep_t a2dp[S_NET_MAX];
|
||||||
int conn_n, paired_n, near_n, a2dp_n;
|
int conn_n, paired_n, near_n, a2dp_n;
|
||||||
int bt_on;
|
int bt_on; /* 真实态三态(-1 未知 / 0 关 / 1 开)——只由 bt_state_now() 写 */
|
||||||
|
unsigned long long bt_on_ts; /* 上面这次查询的时刻(rows/top_sw 同一 tick 只查一次) */
|
||||||
int output_bt; /* 0=DAC/本地,1=蓝牙 */
|
int output_bt; /* 0=DAC/本地,1=蓝牙 */
|
||||||
int full; /* 1=本次 rows() 全量重扫(conn/paired/a2dp);0=仅附近(缓存)+角色(节流) */
|
int full; /* 1=本次 rows() 全量重扫(conn/paired/a2dp);0=仅附近(缓存)+角色(节流) */
|
||||||
char mode[48]; /* bt_role 中文描述(次行摘要) */
|
char mode[48]; /* bt_role 中文描述(次行摘要) */
|
||||||
/* 行副文本池:sub_page_t 行 label/sub 为 const char*,跨 rows() 返回后仍须有效(直到下次
|
/* 行副文本池:sub_page_t 行 label/sub 为 const char*,跨 rows() 返回后仍须有效(直到下次
|
||||||
* rows() 重填)。本地栈缓冲会悬垂(agent-396 根因同类),故格式化 sub 一律写进本池。 */
|
* rows() 重填)。本地栈缓冲会悬垂(agent-396 根因同类),故格式化 sub 一律写进本池。 */
|
||||||
char pool[512];
|
char pool[768];
|
||||||
int pool_off;
|
int pool_off;
|
||||||
} bt_ctx_t;
|
} bt_ctx_t;
|
||||||
|
|
||||||
|
/* M47:settings 里的网络开关布尔值降级为「用户意图的持久化」,不再参与显示判定;
|
||||||
|
* 显示态一律来自 nm_bt_power_state()。 */
|
||||||
|
extern int nm_bt_power_state(void); /* netmgr_ipc.c:daemon 权威 → 直连探测,三态 */
|
||||||
|
|
||||||
/* 格式化并拷入 ctx 行副文本池(返回稳定指针;溢出则绕回——512B 远超单页行数所需)。 */
|
/* 开关收敛参数(宿主测试可在 include 前改小,见 test_bt_page.c)。
|
||||||
|
* 依据:本机慢——hciconfig 一次 0.3~2s,netmgrd 的 on/off 自身还带 ~1.5s 等待;
|
||||||
|
* 250ms 间隔在 4s 内最多探 16 次,既不 20ms 级忙轮询,又能在 1s 内看到首次收敛。 */
|
||||||
|
#ifndef BT_TOGGLE_SETTLE_MS
|
||||||
|
#define BT_TOGGLE_SETTLE_MS 4000
|
||||||
|
#endif
|
||||||
|
#ifndef BT_TOGGLE_POLL_MS
|
||||||
|
#define BT_TOGGLE_POLL_MS 250
|
||||||
|
#endif
|
||||||
|
#define BT_STATE_TTL_MS 800 /* 真实态缓存:rows/top_sw 同一帧共用一次查询 */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* 格式化并拷入 ctx 行副文本池(返回稳定指针;溢出则绕回——768B 远超单页行数所需)。 */
|
||||||
static char *bt_pool(bt_ctx_t *c, const char *fmt, ...)
|
static char *bt_pool(bt_ctx_t *c, const char *fmt, ...)
|
||||||
{
|
{
|
||||||
va_list ap; char tmp[128];
|
va_list ap; char tmp[128];
|
||||||
@@ -95,27 +118,113 @@ static const char *bt_conn_name(const bt_ctx_t *c, const char *mac)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* 顶栏开关当前值 */
|
/* ===== M47:开关「意图 → 真实态」收敛判定(纯函数;无 I/O、无 UI、无全局态)=====
|
||||||
|
* 输入:intent(1=要开/0=要关)、last_real(下发前最后一次可信真实态,-1 未知)、
|
||||||
|
* dispatch_ok(下发命令是否成功)、probe(有界等待后的探测结果,-1 未知)、
|
||||||
|
* timed_out(是否已到等待上限)。
|
||||||
|
* 输出(四个访问器共用同一套判定,拆开只为调用处可读):
|
||||||
|
* meow_toggle_display() 应显示的态(-1 未知 / 0 / 1):有 probe 只认 probe;probe 未知才回退
|
||||||
|
* last_real(上一次「已知」值),两者都未知 → -1。
|
||||||
|
* meow_toggle_pending() 1=还要继续等:下发成功、未超时、且 probe 未等于意图。
|
||||||
|
* 注意「probe 未知」也算没收敛——绝不因为 last_real 恰好等于意图就收工。
|
||||||
|
* meow_toggle_rollback() 1=意图未达成(显示态 != 意图)→ UI/settings 必须回滚到真实态。
|
||||||
|
* meow_toggle_error() 1=收尾后仍未确认 → 必须给用户可见的错误提示(不许静默失败)。
|
||||||
|
* 判定矩阵见 test_bt_page.c「收敛矩阵」;wifi_page.c 复用同一实现。 */
|
||||||
|
static void mtog_calc(int intent, int last_real, int dispatch_ok, int probe, int timed_out,
|
||||||
|
int *disp, int *pending, int *rollback, int *error)
|
||||||
|
{
|
||||||
|
int want = intent ? 1 : 0;
|
||||||
|
int d = (probe >= 0) ? (probe ? 1 : 0)
|
||||||
|
: ((last_real >= 0) ? (last_real ? 1 : 0) : -1);
|
||||||
|
int confirmed = (probe >= 0) && ((probe ? 1 : 0) == want);
|
||||||
|
*disp = d;
|
||||||
|
*pending = (dispatch_ok && !timed_out && !confirmed) ? 1 : 0;
|
||||||
|
*rollback = (d == want) ? 0 : 1;
|
||||||
|
*error = (!confirmed && !*pending) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int meow_toggle_display(int intent, int last_real, int dispatch_ok, int probe, int timed_out)
|
||||||
|
{
|
||||||
|
int d, p, r, e;
|
||||||
|
mtog_calc(intent, last_real, dispatch_ok, probe, timed_out, &d, &p, &r, &e);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
int meow_toggle_pending(int intent, int last_real, int dispatch_ok, int probe, int timed_out)
|
||||||
|
{
|
||||||
|
int d, p, r, e;
|
||||||
|
mtog_calc(intent, last_real, dispatch_ok, probe, timed_out, &d, &p, &r, &e);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
int meow_toggle_rollback(int intent, int last_real, int dispatch_ok, int probe, int timed_out)
|
||||||
|
{
|
||||||
|
int d, p, r, e;
|
||||||
|
mtog_calc(intent, last_real, dispatch_ok, probe, timed_out, &d, &p, &r, &e);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
int meow_toggle_error(int intent, int last_real, int dispatch_ok, int probe, int timed_out)
|
||||||
|
{
|
||||||
|
int d, p, r, e;
|
||||||
|
mtog_calc(intent, last_real, dispatch_ok, probe, timed_out, &d, &p, &r, &e);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 真实态(带 800ms 缓存:top_sw 与 rows 在同一帧各查一次,别为同一问题问 daemon 两遍)。 */
|
||||||
|
static int bt_state_now(bt_ctx_t *c, int force)
|
||||||
|
{
|
||||||
|
unsigned long long t = now_ms();
|
||||||
|
if (!force && c->bt_on_ts && t - c->bt_on_ts < BT_STATE_TTL_MS) return c->bt_on;
|
||||||
|
c->bt_on = nm_bt_power_state();
|
||||||
|
c->bt_on_ts = t;
|
||||||
|
return c->bt_on;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 顶栏开关当前值:只报真实态(-1=未知,subpage 画「?」第三态) */
|
||||||
static int bt_top_sw(void *ctx, int *on)
|
static int bt_top_sw(void *ctx, int *on)
|
||||||
{
|
{
|
||||||
(void)ctx;
|
bt_ctx_t *c = ctx;
|
||||||
int v = 0;
|
*on = bt_state_now(c, 0);
|
||||||
if (meow_settings_get_bool("network.bluetooth_enabled", &v) != 0) v = 0;
|
|
||||||
*on = v;
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 顶栏开关切换:写设置 + on/off(netmgrd IPC,回退直连 hciconfig)+ 触发扫描 + 同步状态栏 */
|
/* 顶栏开关切换:落盘意图 → 下发 → 有界等待真实态收敛 → 用真实态定稿/回滚 + 可见错误提示。
|
||||||
|
* 绝不在下发后就直接把 UI 设成用户想要的样子(那是「显示意图」而不是「显示状态」)。 */
|
||||||
static void bt_top_sw_set(void *ctx, int on)
|
static void bt_top_sw_set(void *ctx, int on)
|
||||||
{
|
{
|
||||||
(void)ctx;
|
bt_ctx_t *c = ctx;
|
||||||
meow_settings_set_bool("network.bluetooth_enabled", on);
|
int want = on ? 1 : 0;
|
||||||
nm_bt_onoff(on);
|
int last_real = bt_state_now(c, 0);
|
||||||
g_sys.bt_on = on;
|
meow_settings_set_bool("network.bluetooth_enabled", want); /* 意图持久化 */
|
||||||
bt_role_refresh(1);
|
|
||||||
paint_sb_right();
|
|
||||||
meow_settings_save();
|
meow_settings_save();
|
||||||
if (on) nm_bt_scan_on();
|
int dispatch_ok = (nm_bt_onoff(want) == MEOW_OK);
|
||||||
|
unsigned long long deadline = now_ms() + BT_TOGGLE_SETTLE_MS;
|
||||||
|
int probe = bt_state_now(c, 1), timed_out = 0;
|
||||||
|
while (meow_toggle_pending(want, last_real, dispatch_ok, probe, timed_out)) {
|
||||||
|
if (now_ms() >= deadline) { timed_out = 1; break; }
|
||||||
|
usleep(BT_TOGGLE_POLL_MS * 1000);
|
||||||
|
probe = bt_state_now(c, 1);
|
||||||
|
}
|
||||||
|
int disp = meow_toggle_display(want, last_real, dispatch_ok, probe, timed_out);
|
||||||
|
int rb = meow_toggle_rollback(want, last_real, dispatch_ok, probe, timed_out);
|
||||||
|
int err = meow_toggle_error(want, last_real, dispatch_ok, probe, timed_out);
|
||||||
|
c->bt_on = disp; c->bt_on_ts = now_ms();
|
||||||
|
/* 回滚语义:settings 只留「已确认的真实态」——失败/超时不留一个骗人的意图值 */
|
||||||
|
meow_settings_set_bool("network.bluetooth_enabled", disp == 1);
|
||||||
|
meow_settings_save();
|
||||||
|
bt_role_refresh(1); /* 先刷新角色(它会写 g_sys.bt_on),再用真实态覆盖 */
|
||||||
|
if (disp >= 0) g_sys.bt_on = disp;
|
||||||
|
paint_sb_right();
|
||||||
|
if (disp == 1) nm_bt_scan_on();
|
||||||
|
logmsg("bt_page: toggle want=%d ok=%d probe=%d to=%d disp=%d rollback=%d err=%d",
|
||||||
|
want, dispatch_ok, probe, timed_out, disp, rb, err);
|
||||||
|
if (err) {
|
||||||
|
char line[96];
|
||||||
|
const char *txt[1]; txt[0] = line;
|
||||||
|
if (disp < 0) snprintf(line, sizeof line, "无法确认蓝牙状态(服务/控制器无响应)");
|
||||||
|
else snprintf(line, sizeof line, "切换未生效:当前仍为%s(%dms 内未确认)",
|
||||||
|
disp ? "开启" : "关闭", BT_TOGGLE_SETTLE_MS);
|
||||||
|
s_dd_modal("蓝牙", txt, 1, -1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -126,7 +235,7 @@ 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->pool_off = 0; /* 行副文本池绕回(上轮行字符串已被重填替换) */
|
c->pool_off = 0; /* 行副文本池绕回(上轮行字符串已被重填替换) */
|
||||||
c->bt_on = nm_bt_powered(); /* IPC status;回退直连 hci_powered() */
|
c->bt_on = bt_state_now(c, 0); /* 真实态三态(未知名 -1);见文件头 M47 说明 */
|
||||||
c->output_bt = 0;
|
c->output_bt = 0;
|
||||||
if (meow_settings_get_bool("audio.output_bt", &c->output_bt) != 0) c->output_bt = 0;
|
if (meow_settings_get_bool("audio.output_bt", &c->output_bt) != 0) c->output_bt = 0;
|
||||||
if (full || c->conn_n < 0) { /* 首次/动作后:全量重扫 conn/paired/a2dp(全权威) */
|
if (full || c->conn_n < 0) { /* 首次/动作后:全量重扫 conn/paired/a2dp(全权威) */
|
||||||
@@ -148,6 +257,13 @@ static int bt_rows(void *ctx, sub_row_t *rbuf, int max)
|
|||||||
rbuf[n].kind = SUB_HDR; rbuf[n].label = "网络管理服务未运行(直连模式)";
|
rbuf[n].kind = SUB_HDR; rbuf[n].label = "网络管理服务未运行(直连模式)";
|
||||||
rbuf[n].sub = NULL; rbuf[n].on = 0; rbuf[n].act = 0; n++;
|
rbuf[n].sub = NULL; rbuf[n].on = 0; rbuf[n].act = 0; n++;
|
||||||
}
|
}
|
||||||
|
/* 第三态显式呈现:真实态未知 ≠ 已关。顶栏开关此时也画「?」(subpage.c),这里再给一行
|
||||||
|
* 文字说明,避免用户把「不知道」读成「关着」。 */
|
||||||
|
if (c->bt_on < 0 && n < max) {
|
||||||
|
rbuf[n].kind = SUB_ROW; rbuf[n].label = "蓝牙状态未知";
|
||||||
|
rbuf[n].sub = "无法读取开关状态(服务/控制器无响应)";
|
||||||
|
rbuf[n].on = 0; rbuf[n].act = -2; n++;
|
||||||
|
}
|
||||||
|
|
||||||
/* ② A2DP 状态摘要(设备名·codec·采样率·输出=蓝牙/DAC) */
|
/* ② A2DP 状态摘要(设备名·codec·采样率·输出=蓝牙/DAC) */
|
||||||
{
|
{
|
||||||
@@ -270,10 +386,14 @@ static int bt_act(void *ctx, int idx, int act)
|
|||||||
char mac[24] = "";
|
char mac[24] = "";
|
||||||
/* 输出目标/编解码器:与本页设备行 act 段(CONN/PAIRED/NEAR base)不冲突,先处理 */
|
/* 输出目标/编解码器:与本页设备行 act 段(CONN/PAIRED/NEAR base)不冲突,先处理 */
|
||||||
if (act == BT_ACT_OUTPUT) {
|
if (act == BT_ACT_OUTPUT) {
|
||||||
|
/* 注意返回值语义:return 1 = 「请重绘/重填行」,**不是**「切换已生效」。本分支只写
|
||||||
|
* settings(audiod 仅启动时读 audio.output_bt),在线切需要 MSG_AUDIO_OUTPUT,
|
||||||
|
* 属 M46 输出目标单的范围(M45 finding: 20261111-survey-m45-btctl-bug-bt-settings-audiod)。
|
||||||
|
* 这里不假装成功:不发 IPC、不写 g_sys,只落偏好。 */
|
||||||
c->output_bt = !c->output_bt;
|
c->output_bt = !c->output_bt;
|
||||||
meow_settings_set_bool("audio.output_bt", c->output_bt);
|
meow_settings_set_bool("audio.output_bt", c->output_bt);
|
||||||
meow_settings_save();
|
meow_settings_save();
|
||||||
logmsg("bt_page: output_target=%s", c->output_bt ? "bt" : "dac");
|
logmsg("bt_page: output_target=%s (preference only; audiod reads at start)", c->output_bt ? "bt" : "dac");
|
||||||
return 1; /* 重绘 + 重填行 */
|
return 1; /* 重绘 + 重填行 */
|
||||||
}
|
}
|
||||||
if (act == BT_ACT_CODEC) {
|
if (act == BT_ACT_CODEC) {
|
||||||
|
|||||||
@@ -11,16 +11,53 @@
|
|||||||
# - launcher 与 SDK 共用同一 meow_font.o(已打进 meow_ui.a)——不再单独链 meow_font.a。
|
# - launcher 与 SDK 共用同一 meow_font.o(已打进 meow_ui.a)——不再单独链 meow_font.a。
|
||||||
# - MEOWOS_VERSION 0.7.21 → 0.7.23(M2.28)。
|
# - MEOWOS_VERSION 0.7.21 → 0.7.23(M2.28)。
|
||||||
#
|
#
|
||||||
# Build(在 patch_v2/meow_launcherd 下):
|
# 用法(在 patch_v2/meow_launcherd 下或任意目录):
|
||||||
# zig-mipsel-cc -I../sdk -Os -static main_menu.c statusbar.c fb_compositor.c settings_page.c \
|
# bash build_launcher.sh # 交叉编译 meow_launcherd(mipsel 静态)
|
||||||
# power_menu.c notif_center.c app_launcher.c audio.c backlight.c net.c about.c \
|
# bash build_launcher.sh test # 宿主 gcc 跑本目录 affected 单测(test_bt_page / test_netmgr_ipc)
|
||||||
# font_theme.c keys.c util.c meow_touch.c meow_settings.c meow_notif.c \
|
#
|
||||||
# ../sdk/meow_ui.a <sysroot>/lib/libz.a -lpthread -o meow_launcherd
|
# 工具链:优先环境变量 ZIGCC / MIPS_SYSROOT(M47 起),否则退回历史固定路径 $HOME/Project/R3ProII。
|
||||||
|
# export ZIGCC=$HOME/.tools/zig-mipsel-cc
|
||||||
set -e
|
set -e
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
ROOT="$HOME/Project/R3ProII"
|
|
||||||
CC="$ROOT/.tools/zig-mipsel-cc"
|
CC="${ZIGCC:-$HOME/.tools/zig-mipsel-cc}"
|
||||||
SYSROOT="$ROOT/.tools/mipsel-linux-muslsf-cross/mipsel-linux-muslsf"
|
[ -x "$CC" ] || CC="$HOME/Project/R3ProII/.tools/zig-mipsel-cc"
|
||||||
|
SYSROOT="${MIPS_SYSROOT:-$HOME/.tools/mipsel-linux-muslsf-cross/mipsel-linux-muslsf}"
|
||||||
|
[ -f "$SYSROOT/lib/libz.a" ] || SYSROOT="$HOME/Project/R3ProII/.tools/mipsel-linux-muslsf-cross/mipsel-linux-muslsf"
|
||||||
|
|
||||||
|
# ---------------- 宿主单测(M47 起提供入口;只跑本目录受影响的测试)----------------
|
||||||
|
run_tests() {
|
||||||
|
local HOSTCC="${HOSTCC:-gcc}"
|
||||||
|
local TMPD rc=0
|
||||||
|
TMPD="$(mktemp -d "${TMPDIR:-/tmp}/meow_launcher_test.XXXXXX")"
|
||||||
|
trap 'rm -rf "$TMPD"' EXIT
|
||||||
|
|
||||||
|
echo "== host test: test_bt_page =="
|
||||||
|
if "$HOSTCC" -I"../sdk" -I. -Wall -Wextra -fsanitize=undefined \
|
||||||
|
test_bt_page.c -o "$TMPD/test_bt_page"; then
|
||||||
|
"$TMPD/test_bt_page" || rc=1
|
||||||
|
else
|
||||||
|
rc=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "== host test: test_netmgr_ipc =="
|
||||||
|
if "$HOSTCC" -I"../sdk" -I. -Wall -Wextra -c netmgr_ipc.c -o "$TMPD/netmgr_ipc.test.o" \
|
||||||
|
&& "$HOSTCC" -I"../sdk" -I. -Wall -Wextra \
|
||||||
|
test_netmgr_ipc.c "$TMPD/netmgr_ipc.test.o" -lpthread -o "$TMPD/test_netmgr_ipc"; then
|
||||||
|
"$TMPD/test_netmgr_ipc" || rc=1
|
||||||
|
else
|
||||||
|
rc=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $rc -eq 0 ]; then echo "== ALL TESTS PASS =="; else echo "!! TESTS FAILED"; fi
|
||||||
|
return $rc
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "$1" = "test" ]; then
|
||||||
|
run_tests
|
||||||
|
exit $?
|
||||||
|
fi
|
||||||
|
|
||||||
[ -f "../sdk/meow_ui.a" ] || { echo "!! 缺 ../sdk/meow_ui.a,先跑 sdk/build_sdk.sh"; exit 1; }
|
[ -f "../sdk/meow_ui.a" ] || { echo "!! 缺 ../sdk/meow_ui.a,先跑 sdk/build_sdk.sh"; exit 1; }
|
||||||
"$CC" -I"../sdk" -Os -static -Wall -Wextra \
|
"$CC" -I"../sdk" -Os -static -Wall -Wextra \
|
||||||
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 \
|
||||||
|
|||||||
Binary file not shown.
@@ -128,6 +128,129 @@ char *popen_read_tmo(int tmo_ms, const char *cmd)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ===== M47:WiFi/BT「真实开/关」三态探测 =========================================
|
||||||
|
* 背景:设置页两个开关此前把 settings 布尔(用户意图)当显示态,读不到时 WiFi 还默认 1,
|
||||||
|
* 于是「开关长什么样」与硬件真实态无关(用户无法判断到底开没开)。M47 起 UI 只认真实态,
|
||||||
|
* 且真实态一律三态:-1=未知 / 0=关 / 1=开。探测失败/解析不出 = 未知,绝不用 0 冒充「已关」,
|
||||||
|
* 也不许用 1 冒充「已开」。netmgrd 可达时 BT 以 daemon 的 bt status 为准(见 netmgr_ipc.c);
|
||||||
|
* 下列函数是「daemon 不可达」时的直连回退,以及 WiFi 的权威探测(netmgrd 的 wifi on/off
|
||||||
|
* 就是 rfkill wlan block/unblock,而 wifi status 只报 wpa_state、不报射频态)。 */
|
||||||
|
|
||||||
|
/* 读一份 sysfs 字符串(去尾部换行);失败返回 -1,buf 置空。 */
|
||||||
|
static int net_read_sys(const char *path, char *buf, int n)
|
||||||
|
{
|
||||||
|
buf[0] = 0;
|
||||||
|
int fd = open(path, O_RDONLY);
|
||||||
|
if (fd < 0) return -1;
|
||||||
|
ssize_t r = read(fd, buf, (size_t)n - 1);
|
||||||
|
close(fd);
|
||||||
|
if (r <= 0) { buf[0] = 0; return -1; }
|
||||||
|
buf[r] = 0;
|
||||||
|
while (r > 0 && (buf[r - 1] == '\n' || buf[r - 1] == '\r')) buf[--r] = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按 rfkill type 读 soft/hard。返回 1=找到 / 0=无该类型 / -1=不可读。
|
||||||
|
* 本机实测有 2 个 wlan + 2 个 bluetooth 节点(rfkill0..3):同一 type 只要有一个被 block,
|
||||||
|
* 射频就不可用,故按「任一 block 即 block」汇总(保守方向:宁可报关,不假装开着)。 */
|
||||||
|
static int rfkill_state(const char *type, int *soft, int *hard)
|
||||||
|
{
|
||||||
|
DIR *d = opendir("/sys/class/rfkill");
|
||||||
|
if (!d) return -1;
|
||||||
|
int found = 0, s_any = 0, h_any = 0;
|
||||||
|
struct dirent *de;
|
||||||
|
while ((de = readdir(d))) {
|
||||||
|
if (strncmp(de->d_name, "rfkill", 6) != 0) continue;
|
||||||
|
char path[128], ty[32];
|
||||||
|
snprintf(path, sizeof path, "/sys/class/rfkill/%s/type", de->d_name);
|
||||||
|
if (net_read_sys(path, ty, sizeof ty) != 0) continue;
|
||||||
|
if (strcmp(ty, type) != 0) continue;
|
||||||
|
snprintf(path, sizeof path, "/sys/class/rfkill/%s/soft", de->d_name);
|
||||||
|
int s = read_file_int(path);
|
||||||
|
snprintf(path, sizeof path, "/sys/class/rfkill/%s/hard", de->d_name);
|
||||||
|
int h = read_file_int(path);
|
||||||
|
if (s < 0 || h < 0) continue; /* 读出失败:当作没找到这一类 */
|
||||||
|
found = 1;
|
||||||
|
if (s) s_any = 1;
|
||||||
|
if (h) h_any = 1;
|
||||||
|
}
|
||||||
|
closedir(d);
|
||||||
|
if (!found) return 0;
|
||||||
|
*soft = s_any; *hard = h_any;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 写 rfkill soft(block=1 关 / 0 开)。返回 0=至少写成功一个;-1=无该类型设备或全写失败。
|
||||||
|
* 同类多个节点全写(只写第一个会漏掉真正绑定驱动的那个)。netmgrd 的 rfkill_set_block
|
||||||
|
* 只写首个匹配节点(见 meow_netmgrd.c:992-1016)——已作为 finding 报给塔,不在本单改。
|
||||||
|
* M47:netmgrd 的 bt on/off 就是「hciconfig + rfkill soft」,直连路径必须写同一份状态,
|
||||||
|
* 否则「从本页关过蓝牙」之后从别的入口 `hciconfig up` 会被内核 rfkill 拦下(开关失真)。 */
|
||||||
|
static int rfkill_set_soft(const char *type, int block)
|
||||||
|
{
|
||||||
|
DIR *d = opendir("/sys/class/rfkill");
|
||||||
|
if (!d) return -1;
|
||||||
|
int rc = -1;
|
||||||
|
struct dirent *de;
|
||||||
|
while ((de = readdir(d))) {
|
||||||
|
if (strncmp(de->d_name, "rfkill", 6) != 0) continue;
|
||||||
|
char path[128], ty[32];
|
||||||
|
snprintf(path, sizeof path, "/sys/class/rfkill/%s/type", de->d_name);
|
||||||
|
if (net_read_sys(path, ty, sizeof ty) != 0) continue;
|
||||||
|
if (strcmp(ty, type) != 0) continue;
|
||||||
|
snprintf(path, sizeof path, "/sys/class/rfkill/%s/soft", de->d_name);
|
||||||
|
int fd = open(path, O_WRONLY);
|
||||||
|
if (fd < 0) continue;
|
||||||
|
char v[2] = { block ? '1' : '0', 0 };
|
||||||
|
if (write(fd, v, 1) == 1) rc = 0; /* 任一成功即视为已下发(不因后一个失败翻回 -1) */
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
closedir(d);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* WiFi 射频真实态:-1 未知 / 0 关 / 1 开。
|
||||||
|
* 1) 有 wlan rfkill 设备(netmgrd 的 wifi on/off 就是写它):hard/soft 任一 block = 关;
|
||||||
|
* 未 block 时接口存在才算开(无 wlan0 = 驱动没载,无法判定 → 未知)。
|
||||||
|
* 2) 无 rfkill 设备:退 wlan0 管理态 IFF_UP(与状态栏 wlan_radio_up 同源)。
|
||||||
|
* 3) 都读不到 = 未知(不当「已关」)。 */
|
||||||
|
int net_wifi_power_probe(void)
|
||||||
|
{
|
||||||
|
int soft = 0, hard = 0;
|
||||||
|
int rk = rfkill_state("wlan", &soft, &hard);
|
||||||
|
if (rk == 1) {
|
||||||
|
if (hard || soft) return 0;
|
||||||
|
return access("/sys/class/net/wlan0", F_OK) == 0 ? 1 : -1;
|
||||||
|
}
|
||||||
|
if (rk < 0) return -1;
|
||||||
|
if (access("/sys/class/net/wlan0", F_OK) != 0) return -1;
|
||||||
|
char b[16];
|
||||||
|
if (net_read_sys("/sys/class/net/wlan0/flags", b, sizeof b) != 0) return -1;
|
||||||
|
return (int)(strtoul(b, NULL, 16) & 0x1UL) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* BT 控制器真实态:-1 未知 / 0 关 / 1 开。
|
||||||
|
* 权威 = hciconfig hci0 的 UP/DOWN 状态行(与 netmgrd bt_status 的回退解析同源)。
|
||||||
|
* 命令超时/无输出/既无 UP 也无 DOWN(如 hci0 不存在)= 未知。 */
|
||||||
|
int net_bt_power_probe(void)
|
||||||
|
{
|
||||||
|
char *o = popen_read_tmo(1800,
|
||||||
|
"PATH=/bin:/sbin:/usr/bin:/usr/sbin hciconfig -a hci0 2>/dev/null");
|
||||||
|
if (!o) return -1;
|
||||||
|
int r = -1;
|
||||||
|
char *line = o;
|
||||||
|
while (line && *line) {
|
||||||
|
char *eol = strchr(line, '\n'); if (eol) *eol = 0;
|
||||||
|
char *p = line;
|
||||||
|
while (*p == ' ' || *p == '\t') p++;
|
||||||
|
if (!strncmp(p, "DOWN", 4)) { r = 0; break; }
|
||||||
|
if (!strncmp(p, "UP", 2)) { r = 1; break; }
|
||||||
|
line = eol ? eol + 1 : NULL;
|
||||||
|
}
|
||||||
|
free(o);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* 十六进制字符值('0'-'9'/'a'-'f'/'A'-'F');非法返回 -1。避免依赖 ctype.h(musl/内存受限)。
|
/* 十六进制字符值('0'-'9'/'a'-'f'/'A'-'F');非法返回 -1。避免依赖 ctype.h(musl/内存受限)。
|
||||||
* M2.40:wpa_cli 对非 ASCII SSID 输出 "\xNN" 转义文字(`\`+x+两位hex 的纯文字,非真字节),
|
* M2.40:wpa_cli 对非 ASCII SSID 输出 "\xNN" 转义文字(`\`+x+两位hex 的纯文字,非真字节),
|
||||||
* 需先还原成真实 UTF-8 字节才能进界面文本。 */
|
* 需先还原成真实 UTF-8 字节才能进界面文本。 */
|
||||||
@@ -733,7 +856,11 @@ int s_net_get(const srow_t *r, char *buf, int len)
|
|||||||
|
|
||||||
|
|
||||||
/* 网络动作开关:wifi/bluetooth 开/关(写 wlan0/hci0;立即生效,异步刷新缓存)。
|
/* 网络动作开关:wifi/bluetooth 开/关(写 wlan0/hci0;立即生效,异步刷新缓存)。
|
||||||
* 走 system(wifi_up.sh 自身后台化子进程、ip link/hciconfig 均为快 ioctl)。 */
|
* 走 system(wifi_up.sh 自身后台化子进程、ip link/hciconfig 均为快 ioctl)。
|
||||||
|
* M47:蓝牙补上 rfkill soft(与 netmgrd 的 bt on/off 同一语义)——此前本函数只发 hciconfig,
|
||||||
|
* 于是「在蓝牙页关过(rfkill soft=1)→ 再从通知中心/设置主页打开」时 `hciconfig up` 被内核
|
||||||
|
* rfkill 拦下、settings 却已写 1,开关显示 ON 实际 OFF(M45 盘点的开关失真)。开:先解 rfkill
|
||||||
|
* 再 up(顺序颠倒会先失败一次);关:先 down 再 block(与 netmgrd 一致)。 */
|
||||||
void s_net_toggle_wifi(int on)
|
void s_net_toggle_wifi(int on)
|
||||||
{
|
{
|
||||||
if (on) system("PATH=/bin:/sbin:/usr/bin:/usr/sbin /usr/bin/wifi_up.sh");
|
if (on) system("PATH=/bin:/sbin:/usr/bin:/usr/sbin /usr/bin/wifi_up.sh");
|
||||||
@@ -742,24 +869,33 @@ void s_net_toggle_wifi(int on)
|
|||||||
|
|
||||||
void s_net_toggle_bt(int on)
|
void s_net_toggle_bt(int on)
|
||||||
{
|
{
|
||||||
if (on) system("hciconfig hci0 up 2>/dev/null");
|
if (on) {
|
||||||
else system("hciconfig hci0 down 2>/dev/null");
|
rfkill_set_soft("bluetooth", 0);
|
||||||
|
system("hciconfig hci0 up 2>/dev/null");
|
||||||
|
} else {
|
||||||
|
system("hciconfig hci0 down 2>/dev/null");
|
||||||
|
rfkill_set_soft("bluetooth", 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* 进入设置页时把 network.wifi_enabled/bluetooth_enabled 同步为真实硬件态,
|
/* 进入设置页时把 network.wifi_enabled/bluetooth_enabled 同步为真实硬件态,
|
||||||
* 让开关行反映 reality(仅同步内存 + 刷新缓存;不落盘 —— s_commit 的 save 才真正写)。
|
* 让开关行反映 reality(仅同步内存 + 刷新缓存;不落盘 —— s_commit 的 save 才真正写)。
|
||||||
* settings 未 init/取值失败则不动(保默认)。 */
|
* settings 未 init/取值失败则不动(保默认)。
|
||||||
|
* M47(M45 盘点):WiFi 判据不再是 wlan_connected()(=operstate up,即「已关联」)——网卡 up
|
||||||
|
* 但未关联时 operstate=dormant,旧写法会把 key 改成 0、WiFi 页开关显示 OFF 而射频明明开着;
|
||||||
|
* 改用 net_wifi_power_probe()(rfkill/网卡 up)。BT 改用三态探测:读不到(-1)就不写,
|
||||||
|
* 不拿 0 猜「已关」。 */
|
||||||
void s_net_sync_from_sys(void)
|
void s_net_sync_from_sys(void)
|
||||||
{
|
{
|
||||||
int cur = 0;
|
int cur = 0;
|
||||||
if (meow_settings_get_bool("network.wifi_enabled", &cur) == 0) {
|
if (meow_settings_get_bool("network.wifi_enabled", &cur) == 0) {
|
||||||
int real = wlan_connected() ? 1 : 0;
|
int real = net_wifi_power_probe();
|
||||||
if (real != cur) meow_settings_set_bool("network.wifi_enabled", real);
|
if (real >= 0 && real != cur) meow_settings_set_bool("network.wifi_enabled", real);
|
||||||
}
|
}
|
||||||
if (meow_settings_get_bool("network.bluetooth_enabled", &cur) == 0) {
|
if (meow_settings_get_bool("network.bluetooth_enabled", &cur) == 0) {
|
||||||
int real = hci_powered() ? 1 : 0;
|
int real = net_bt_power_probe();
|
||||||
if (real != cur) meow_settings_set_bool("network.bluetooth_enabled", real);
|
if (real >= 0 && real != cur) meow_settings_set_bool("network.bluetooth_enabled", real);
|
||||||
}
|
}
|
||||||
/* 只同步开关(快)+ 异步 kick;状态/列表由 run_settings_page 主循环 pump,绝不阻塞入口。 */
|
/* 只同步开关(快)+ 异步 kick;状态/列表由 run_settings_page 主循环 pump,绝不阻塞入口。 */
|
||||||
s_net_refresh_kick();
|
s_net_refresh_kick();
|
||||||
|
|||||||
@@ -15,6 +15,11 @@
|
|||||||
#define NETMGR_SOCK_DEFAULT "/usr/data/meow_net.sock"
|
#define NETMGR_SOCK_DEFAULT "/usr/data/meow_net.sock"
|
||||||
/* NETMGR_UNREACH 定义在 meow_launcher_internal.h(测试/调用方共用) */
|
/* NETMGR_UNREACH 定义在 meow_launcher_internal.h(测试/调用方共用) */
|
||||||
|
|
||||||
|
/* M47:net.c 的直连三态探测(-1 未知 / 0 关 / 1 开)。声明在此而非头文件——头文件不在本单
|
||||||
|
* 改动范围;两处定义与用法同属 launcher 网络线。 */
|
||||||
|
int net_bt_power_probe(void);
|
||||||
|
int net_wifi_power_probe(void);
|
||||||
|
|
||||||
/* IPC 读超时(ms)。netmgrd 单线程 poll,connect/connecth 会阻塞 ~10s/~15s 有界等待
|
/* IPC 读超时(ms)。netmgrd 单线程 poll,connect/connecth 会阻塞 ~10s/~15s 有界等待
|
||||||
* wpa_state=COMPLETED(bug1),scan 内含 1.5s 触发等待(P3)——客户端 per-command 超时需
|
* wpa_state=COMPLETED(bug1),scan 内含 1.5s 触发等待(P3)——客户端 per-command 超时需
|
||||||
* 覆盖这些同步等待,其余读级子命令维持 1.5s 不被卡死。 */
|
* 覆盖这些同步等待,其余读级子命令维持 1.5s 不被卡死。 */
|
||||||
@@ -552,6 +557,32 @@ int nm_bt_powered(void)
|
|||||||
return atoi(v);
|
return atoi(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== M47:设置页开关的「真实态」三态查询(-1 未知 / 0 关 / 1 开)=====
|
||||||
|
* 与 nm_bt_powered()/nm_wifi_connected() 的区别:那两个把「问不到」塌缩成 0(看起来像
|
||||||
|
* 「已关」),正是开关显示不可信的根因之一。M47 起设置页开关只用下面两个函数。 */
|
||||||
|
int nm_bt_power_state(void)
|
||||||
|
{
|
||||||
|
char text[192];
|
||||||
|
int rc = netmgr_bt("status", NULL, text, sizeof text);
|
||||||
|
if (rc == NETMGR_UNREACH) return net_bt_power_probe(); /* 直连回退;失败=未知 */
|
||||||
|
if (rc != MEOW_OK) return -1; /* daemon 报错:不知道就是不知道 */
|
||||||
|
char v[8];
|
||||||
|
nm_val(text, "available=", v, sizeof v, 0);
|
||||||
|
if (v[0] && atoi(v) == 0) return 0; /* 无控制器 → 必为关 */
|
||||||
|
nm_val(text, "powered=", v, sizeof v, 0);
|
||||||
|
if (!v[0]) return -1; /* 无 powered 字段 → 未知 */
|
||||||
|
return atoi(v) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* WiFi 射频真实态(三态)。netmgrd 的 `wifi status` 只报 wpa_state/连接态,不报射频开关
|
||||||
|
* (见 meow_netmgrd.c wifi_status,字段仅 state/connected/ssid/ip/rssi),无法据此判断
|
||||||
|
* 「wifi 开关」;而 daemon 的 wifi on/off 恰好就是 rfkill wlan block/unblock,所以射频态
|
||||||
|
* 取本地 rfkill(回退 wlan0 IFF_UP)——与 daemon 改的是同一份内核状态,且探测失败=未知。 */
|
||||||
|
int nm_wifi_power_state(void)
|
||||||
|
{
|
||||||
|
return net_wifi_power_probe();
|
||||||
|
}
|
||||||
|
|
||||||
int nm_bt_onoff(int on)
|
int nm_bt_onoff(int on)
|
||||||
{
|
{
|
||||||
char text[128];
|
char text[128];
|
||||||
|
|||||||
@@ -541,7 +541,8 @@ static sub_page_t *g_sp; /* 当前运行子页(rows/act/detai
|
|||||||
static sub_row_t g_rows[SUB_ROWS_MAX]; /* 行缓冲:rows() 一次性填满,listview bind 只读 */
|
static sub_row_t g_rows[SUB_ROWS_MAX]; /* 行缓冲:rows() 一次性填满,listview bind 只读 */
|
||||||
static int g_nrows = 0; /* 当前行数(listview vsource count 实时读) */
|
static int g_nrows = 0; /* 当前行数(listview vsource count 实时读) */
|
||||||
static int g_top_sw_en = 0; /* 本页是否显示顶开关 */
|
static int g_top_sw_en = 0; /* 本页是否显示顶开关 */
|
||||||
static int g_top_on = 0; /* 顶开关当前值 */
|
static int g_top_on = 0; /* 顶开关当前值(0/1;未知时停 0 并置 g_top_sw_unknown) */
|
||||||
|
static int g_top_sw_unknown = 0; /* M47 第三态:真实态未知(页回调 top_sw 回 -1) */
|
||||||
|
|
||||||
static meow_widget *g_tb = NULL; /* topappbar */
|
static meow_widget *g_tb = NULL; /* topappbar */
|
||||||
static meow_widget *g_sw = NULL; /* 顶开关(仅在有顶开关的页创建/使用) */
|
static meow_widget *g_sw = NULL; /* 顶开关(仅在有顶开关的页创建/使用) */
|
||||||
@@ -668,22 +669,29 @@ static void sp_widgets_create(void)
|
|||||||
g_sp_init = 1;
|
g_sp_init = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 顶开关控件置态(M47:未知 -1 时 SDK 开关只有两态,停在「关」+ 灰色「?」角标区分)。 */
|
||||||
|
static void sp_top_sw_apply(void)
|
||||||
|
{
|
||||||
|
if (g_sw) meow_switch_set(g_sw, g_top_on == 1 ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
/* 逐页配置:topappbar 标题 + 顶开关(按需创建/置态)+ listview 行数。 */
|
/* 逐页配置:topappbar 标题 + 顶开关(按需创建/置态)+ listview 行数。 */
|
||||||
static void sp_configure_page(const sub_page_t *p, int top_sw_en, int top_on)
|
static void sp_configure_page(const sub_page_t *p, int top_sw_en, int top_on)
|
||||||
{
|
{
|
||||||
if (p->title && g_tb) meow_topappbar_set_title(g_tb, p->title);
|
if (p->title && g_tb) meow_topappbar_set_title(g_tb, p->title);
|
||||||
g_top_sw_en = top_sw_en;
|
g_top_sw_en = top_sw_en;
|
||||||
g_top_on = top_on;
|
/* M47:top_sw 回调的 *on 是三态(-1 未知 / 0 / 1),见 bt_page/wifi_page。 */
|
||||||
|
g_top_sw_unknown = (top_on < 0) ? 1 : 0;
|
||||||
|
g_top_on = (top_on == 1) ? 1 : 0;
|
||||||
if (top_sw_en) {
|
if (top_sw_en) {
|
||||||
if (!g_sw) {
|
if (!g_sw) {
|
||||||
meow_ui_activity_push(SUB_SENTINEL_ACT); /* owner 钉哨兵(命中闸门) */
|
meow_ui_activity_push(SUB_SENTINEL_ACT); /* owner 钉哨兵(命中闸门) */
|
||||||
int swx = fb_xres - SUB_PAD_X - 56; /* meow_switch 固定 56×32 */
|
int swx = fb_xres - SUB_PAD_X - 56; /* meow_switch 固定 56×32 */
|
||||||
int swy = SUB_TB_Y + (SUB_TB_H - SUB_SW_H) / 2;
|
int swy = SUB_TB_Y + (SUB_TB_H - SUB_SW_H) / 2;
|
||||||
g_sw = meow_switch_create(swx, swy, top_on);
|
g_sw = meow_switch_create(swx, swy, g_top_on);
|
||||||
meow_ui_activity_pop();
|
meow_ui_activity_pop();
|
||||||
} else if (g_sw) {
|
|
||||||
meow_switch_set(g_sw, top_on);
|
|
||||||
}
|
}
|
||||||
|
sp_top_sw_apply();
|
||||||
}
|
}
|
||||||
/* 批③:行完全自绘 on/off —— 页提供 row_draw 用自绘;否则清 NULL 回默认 renderer。 */
|
/* 批③:行完全自绘 on/off —— 页提供 row_draw 用自绘;否则清 NULL 回默认 renderer。 */
|
||||||
if (g_lv) meow_listview_set_item_draw(g_lv, p->row_draw ? sp_lv_draw : NULL, NULL);
|
if (g_lv) meow_listview_set_item_draw(g_lv, p->row_draw ? sp_lv_draw : NULL, NULL);
|
||||||
@@ -697,7 +705,17 @@ static void sp_paint_page(void)
|
|||||||
meow_ui_fill_rect(0, 0, W, H, mt->surface); /* 全屏底 surface */
|
meow_ui_fill_rect(0, 0, W, H, mt->surface); /* 全屏底 surface */
|
||||||
draw_statusbar();
|
draw_statusbar();
|
||||||
if (g_tb) meow_widget_draw(g_tb); /* topappbar(含底部分隔线) */
|
if (g_tb) meow_widget_draw(g_tb); /* topappbar(含底部分隔线) */
|
||||||
if (g_sw && g_top_sw_en) meow_widget_draw(g_sw); /* 顶开关(叠顶栏右缘) */
|
if (g_sw && g_top_sw_en) {
|
||||||
|
meow_widget_draw(g_sw); /* 顶开关(叠顶栏右缘) */
|
||||||
|
if (g_top_sw_unknown) {
|
||||||
|
/* M47 第三态:真实态未知。SDK 开关只有开/关两态,故开关停「关」并在左侧画灰色
|
||||||
|
* 「?」——明确表达「不是关着,是不知道」,而不是拿「关」冒充。 */
|
||||||
|
int qh = meow_ui_text_height(20);
|
||||||
|
int qw = ui_width("?", 20);
|
||||||
|
ui_draw(fb_xres - SUB_PAD_X - 56 - qw - 6,
|
||||||
|
SUB_TB_Y + (SUB_TB_H - qh) / 2, "?", mt->outline, 20);
|
||||||
|
}
|
||||||
|
}
|
||||||
sp_paint_top_action(); /* 批③:顶栏右侧动作按钮 */
|
sp_paint_top_action(); /* 批③:顶栏右侧动作按钮 */
|
||||||
int lvx, lvy, lvw, lvh;
|
int lvx, lvy, lvw, lvh;
|
||||||
sp_lv_rect(&lvx, &lvy, &lvw, &lvh);
|
sp_lv_rect(&lvx, &lvy, &lvw, &lvh);
|
||||||
@@ -710,13 +728,40 @@ static void sp_paint_page(void)
|
|||||||
s_ui_theme()->outline, 18);
|
s_ui_theme()->outline, 18);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 顶部开关切换:写设置 + 生效 + 触发重扫 + 重填行。 */
|
/* 顶开关回读真实态(M47:UI 只认权威回调,不做「点了就翻面」的乐观更新)。 */
|
||||||
|
static void sp_top_sw_resync(void)
|
||||||
|
{
|
||||||
|
if (!g_sp || !g_sp->top_sw) return;
|
||||||
|
int on = 0;
|
||||||
|
if (!g_sp->top_sw(g_sp->ctx, &on)) { g_top_sw_en = 0; return; }
|
||||||
|
g_top_sw_en = 1;
|
||||||
|
g_top_sw_unknown = (on < 0) ? 1 : 0;
|
||||||
|
g_top_on = (on == 1) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 「待确认」态:开关保持上一次已确认外观,内容区顶出示同步条——与稳定态肉眼可区分。
|
||||||
|
* 有界等待期间主循环阻塞在 top_sw_set 内,所以必须先画一帧再下发。 */
|
||||||
|
static void sp_paint_switching(int want)
|
||||||
|
{
|
||||||
|
const meow_theme *mt = meow_theme_get();
|
||||||
|
char line[64];
|
||||||
|
snprintf(line, sizeof line, "正在%s…(等待设备确认)", want ? "开启" : "关闭");
|
||||||
|
int h = 32, y = SUB_CONT_TOP;
|
||||||
|
meow_ui_fill_rect(0, y, fb_xres, h, mt->surfaceContainerHigh);
|
||||||
|
meow_ui_text_center(fb_xres / 2, y + (h - meow_ui_text_height(18)) / 2, line, 18, mt->primary);
|
||||||
|
meow_dirty_full();
|
||||||
|
meow_ui_flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 顶部开关切换:待确认态 → 页内下发+有界等待+失败弹窗 → 回读真实态 → 重填行。 */
|
||||||
static void sp_top_sw_toggle(void)
|
static void sp_top_sw_toggle(void)
|
||||||
{
|
{
|
||||||
if (!g_sp || !g_sp->top_sw_set) return;
|
if (!g_sp || !g_sp->top_sw_set) return;
|
||||||
g_sp->top_sw_set(g_sp->ctx, !g_top_on);
|
int want = (g_top_on == 1) ? 0 : 1; /* 未知(-1) 视同「关」:点一下表示「请打开」 */
|
||||||
g_top_on = !g_top_on;
|
sp_paint_switching(want);
|
||||||
if (g_sw) meow_switch_set(g_sw, g_top_on);
|
g_sp->top_sw_set(g_sp->ctx, want); /* 页内:下发 + 有界轮询真实态 + 失败回滚/弹窗 */
|
||||||
|
sp_top_sw_resync(); /* 定稿只认真实态 */
|
||||||
|
sp_top_sw_apply();
|
||||||
if (g_sp->rows) {
|
if (g_sp->rows) {
|
||||||
g_nrows = g_sp->rows(g_sp->ctx, g_rows, SUB_ROWS_MAX);
|
g_nrows = g_sp->rows(g_sp->ctx, g_rows, SUB_ROWS_MAX);
|
||||||
if (g_nrows > SUB_ROWS_MAX) g_nrows = SUB_ROWS_MAX;
|
if (g_nrows > SUB_ROWS_MAX) g_nrows = SUB_ROWS_MAX;
|
||||||
@@ -977,7 +1022,8 @@ int meow_subpage_run(sub_page_t *p)
|
|||||||
}
|
}
|
||||||
if (g_top_sw_en && g_sw && meow_widget_hit(g_sw, tx, ty)) {
|
if (g_top_sw_en && g_sw && meow_widget_hit(g_sw, tx, ty)) {
|
||||||
sp_top_sw_toggle();
|
sp_top_sw_toggle();
|
||||||
last_act = now;
|
last_act = now_ms(); /* 切换内可能阻塞数秒:取新鲜时刻,
|
||||||
|
* 否则空闲息屏会按陈旧时刻提前触发 */
|
||||||
need_paint = 1;
|
need_paint = 1;
|
||||||
if (tfd >= 0) { unsigned char e[16]; while (read(tfd, e, sizeof e) == 16) {} }
|
if (tfd >= 0) { unsigned char e[16]; while (read(tfd, e, sizeof e) == 16) {} }
|
||||||
continue;
|
continue;
|
||||||
@@ -1035,6 +1081,14 @@ int meow_subpage_run(sub_page_t *p)
|
|||||||
/* ---- 周期性重扫(live 页:蓝牙附近扫描等驻留更新)---- */
|
/* ---- 周期性重扫(live 页:蓝牙附近扫描等驻留更新)---- */
|
||||||
if (g_sp && g_sp->live && now - last_live >= 2000) {
|
if (g_sp && g_sp->live && now - last_live >= 2000) {
|
||||||
last_live = now;
|
last_live = now;
|
||||||
|
/* M47:顶开关也按 live 周期回读真实态——外部(其它页面/守护进程)改了开关,
|
||||||
|
* 本页驻留期间也要跟着变,而不是停在进入页面时那一刻。 */
|
||||||
|
int sw_before = g_top_on, unk_before = g_top_sw_unknown;
|
||||||
|
if (g_top_sw_en) sp_top_sw_resync();
|
||||||
|
if (g_top_on != sw_before || g_top_sw_unknown != unk_before) {
|
||||||
|
sp_top_sw_apply();
|
||||||
|
need_paint = 1;
|
||||||
|
}
|
||||||
int new_n = g_sp->rows ? g_sp->rows(g_sp->ctx, g_rows, SUB_ROWS_MAX) : 0;
|
int new_n = g_sp->rows ? g_sp->rows(g_sp->ctx, g_rows, SUB_ROWS_MAX) : 0;
|
||||||
if (new_n > SUB_ROWS_MAX) new_n = SUB_ROWS_MAX;
|
if (new_n > SUB_ROWS_MAX) new_n = SUB_ROWS_MAX;
|
||||||
if (new_n != g_nrows) {
|
if (new_n != g_nrows) {
|
||||||
|
|||||||
@@ -8,16 +8,25 @@
|
|||||||
* P2 bt_act 命中 → 正确下发 netmgrd 命令(已连接→disconnect、已配对未连→connect、输出目标
|
* P2 bt_act 命中 → 正确下发 netmgrd 命令(已连接→disconnect、已配对未连→connect、输出目标
|
||||||
* 切换、编解码器循环);
|
* 切换、编解码器循环);
|
||||||
* P3 附近设备仅列未配对(已配对者归「已配对设备」)。
|
* P3 附近设备仅列未配对(已配对者归「已配对设备」)。
|
||||||
|
* P4 M47 开关三态:真实态未知(-1) → top_sw 回 -1 + 行内「蓝牙状态未知」(不当已关);
|
||||||
|
* P5 M47 点击收敛:下发成功且真实态达成 → 定稿、不弹窗;下发失败/超时/未知 → 回滚 + 弹窗;
|
||||||
|
* P6 M47 收敛矩阵(纯函数 meow_toggle_*,真实态×下发结果×探测 全组合)。
|
||||||
*
|
*
|
||||||
* 编译(在 patch_v2/meow_launcherd 下):
|
* 编译(在 patch_v2/meow_launcherd 下):
|
||||||
* gcc -I../sdk -I. -Wall -Wextra -fsanitize=undefined test_bt_page.c -o test_bt_page
|
* gcc -I../sdk -I. -Wall -Wextra -fsanitize=undefined test_bt_page.c -o test_bt_page
|
||||||
* 运行:./test_bt_page
|
* 运行:./test_bt_page(或 `bash build_launcher.sh test`)
|
||||||
*/
|
*/
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <strings.h>
|
#include <strings.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
/* M47:把收敛等待改成宿主可接受的尺度(生产 4000ms/250ms,见 bt_page.c)。 */
|
||||||
|
#define BT_TOGGLE_SETTLE_MS 80
|
||||||
|
#define BT_TOGGLE_POLL_MS 10
|
||||||
|
|
||||||
#include "meow_launcher_internal.h"
|
#include "meow_launcher_internal.h"
|
||||||
#include "bt_page.c" /* bt_rows/bt_act/bt_top_sw/bt_detail 平移到本 TU */
|
#include "bt_page.c" /* bt_rows/bt_act/bt_top_sw/bt_detail 平移到本 TU */
|
||||||
@@ -29,9 +38,12 @@ static bt_ep_t g_eps[8]; static int g_eps_n;
|
|||||||
static char g_last_cmd[128]; /* 记录 act 下发命令,如 "disconnect:<mac>" */
|
static char g_last_cmd[128]; /* 记录 act 下发命令,如 "disconnect:<mac>" */
|
||||||
static int g_output_bt; /* audio.output_bt 桩值 */
|
static int g_output_bt; /* audio.output_bt 桩值 */
|
||||||
static char g_codec[16] = "auto"; /* bt.codec 桩值 */
|
static char g_codec[16] = "auto"; /* bt.codec 桩值 */
|
||||||
|
static int g_bt_state = 1; /* M47:真实态三态桩(-1 未知 / 0 / 1) */
|
||||||
|
static int g_onoff_rc = MEOW_OK; /* M47:nm_bt_onoff 下发结果桩 */
|
||||||
|
static int g_bt_en_setting = 1; /* M47:network.bluetooth_enabled 桩值(意图/回滚观察点) */
|
||||||
|
|
||||||
int nm_bt_powered(void) { return 1; }
|
int nm_bt_power_state(void) { return g_bt_state; }
|
||||||
int nm_bt_onoff(int on) { (void)on; return 0; }
|
int nm_bt_onoff(int on) { (void)on; return g_onoff_rc; }
|
||||||
int nm_bt_connected(meow_net_t *out, int max) {
|
int nm_bt_connected(meow_net_t *out, int max) {
|
||||||
if (max > g_conn_n) max = g_conn_n;
|
if (max > g_conn_n) max = g_conn_n;
|
||||||
memcpy(out, g_conn, (size_t)max * sizeof *out); return max;
|
memcpy(out, g_conn, (size_t)max * sizeof *out); return max;
|
||||||
@@ -65,14 +77,14 @@ void net_bt_mode_str(char *buf, int len) { snprintf(buf, (size_t)len, "主模式
|
|||||||
int meow_settings_get_bool(const char *k, int *v)
|
int meow_settings_get_bool(const char *k, int *v)
|
||||||
{
|
{
|
||||||
if (v) *v = 0;
|
if (v) *v = 0;
|
||||||
if (!strcmp(k, "network.bluetooth_enabled")) { if (v) *v = 1; return 0; }
|
if (!strcmp(k, "network.bluetooth_enabled")) { if (v) *v = g_bt_en_setting; return 0; }
|
||||||
if (!strcmp(k, "audio.output_bt")) { if (v) *v = g_output_bt; return 0; }
|
if (!strcmp(k, "audio.output_bt")) { if (v) *v = g_output_bt; return 0; }
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
int meow_settings_set_bool(const char *k, int v)
|
int meow_settings_set_bool(const char *k, int v)
|
||||||
{
|
{
|
||||||
if (!strcmp(k, "audio.output_bt")) { g_output_bt = v; return 0; }
|
if (!strcmp(k, "audio.output_bt")) { g_output_bt = v; return 0; }
|
||||||
if (!strcmp(k, "network.bluetooth_enabled")) return 0;
|
if (!strcmp(k, "network.bluetooth_enabled")) { g_bt_en_setting = v; return 0; }
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
int meow_settings_get_str(const char *k, char *b, int n)
|
int meow_settings_get_str(const char *k, char *b, int n)
|
||||||
@@ -91,9 +103,26 @@ void paint_sb_right(void) {}
|
|||||||
void logmsg(const char *fmt, ...) { (void)fmt; }
|
void logmsg(const char *fmt, ...) { (void)fmt; }
|
||||||
void bt_role_refresh(int force) { (void)force; }
|
void bt_role_refresh(int force) { (void)force; }
|
||||||
meow_sys_state g_sys;
|
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; }
|
static int g_modal_n = 0; /* M47:可见错误提示(弹窗)计数 */
|
||||||
|
static char g_modal_line[128] = "";
|
||||||
|
int s_dd_modal(const char *title, const char *item[S_NET_MAX], int n, int cur)
|
||||||
|
{
|
||||||
|
(void)title; (void)cur;
|
||||||
|
g_modal_n++;
|
||||||
|
g_modal_line[0] = 0;
|
||||||
|
if (item && n > 0 && item[0]) snprintf(g_modal_line, sizeof g_modal_line, "%s", item[0]);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
int meow_subpage_run(sub_page_t *p) { (void)p; return 0; }
|
int meow_subpage_run(sub_page_t *p) { (void)p; return 0; }
|
||||||
|
|
||||||
|
/* M47:真实时钟(收敛等待用),毫秒单调。 */
|
||||||
|
unsigned long long now_ms(void)
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
|
return (unsigned long long)ts.tv_sec * 1000ULL + (unsigned long long)(ts.tv_nsec / 1000000);
|
||||||
|
}
|
||||||
|
|
||||||
static int g_fail = 0;
|
static int g_fail = 0;
|
||||||
#define CHECK(cond, ...) do { if (cond) { printf(" PASS: " __VA_ARGS__); printf("\n"); } \
|
#define CHECK(cond, ...) do { if (cond) { printf(" PASS: " __VA_ARGS__); printf("\n"); } \
|
||||||
else { printf(" FAIL: " __VA_ARGS__); printf("\n"); g_fail++; } } while (0)
|
else { printf(" FAIL: " __VA_ARGS__); printf("\n"); g_fail++; } } while (0)
|
||||||
@@ -111,6 +140,13 @@ static int find_act(const sub_row_t *r, int n, int from, const char *pref)
|
|||||||
if (r[i].kind == SUB_ACT && r[i].label && !strncmp(r[i].label, pref, strlen(pref))) return i;
|
if (r[i].kind == SUB_ACT && r[i].label && !strncmp(r[i].label, pref, strlen(pref))) return i;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
/* 按 label 精确找任意行(M47 未知态占位行用) */
|
||||||
|
static int row_label(const sub_row_t *r, int n, const char *lbl)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
if (r[i].label && !strcmp(r[i].label, lbl)) return i;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
static void fill_state(void)
|
static void fill_state(void)
|
||||||
{
|
{
|
||||||
@@ -230,6 +266,92 @@ int main(void)
|
|||||||
bt_act(&c, ci, rows[ci].act);
|
bt_act(&c, ci, rows[ci].act);
|
||||||
CHECK(strcmp(g_codec, "SBC") == 0, "编解码器 auto → SBC (实际=%s)", g_codec);
|
CHECK(strcmp(g_codec, "SBC") == 0, "编解码器 auto → SBC (实际=%s)", g_codec);
|
||||||
|
|
||||||
|
printf("== P4 M47 开关三态(未知 ≠ 已关)==\n");
|
||||||
|
int sw = 0;
|
||||||
|
g_bt_state = -1; /* 真实态未知 */
|
||||||
|
memset(&c, 0, sizeof c); c.full = 1; fill_state();
|
||||||
|
CHECK(bt_top_sw(&c, &sw) == 1 && sw == -1, "真实态未知 → top_sw *on=-1 (实际=%d)", sw);
|
||||||
|
n = bt_rows(&c, rows, SUB_ROWS_MAX);
|
||||||
|
CHECK(row_label(rows, n, "蓝牙状态未知") >= 0, "未知态 → 行内含「蓝牙状态未知」");
|
||||||
|
g_bt_state = 0; /* 真实态为关 */
|
||||||
|
memset(&c, 0, sizeof c); c.full = 1;
|
||||||
|
sw = -2;
|
||||||
|
bt_top_sw(&c, &sw);
|
||||||
|
CHECK(sw == 0, "真实态关 → top_sw *on=0 (实际=%d)", sw);
|
||||||
|
n = bt_rows(&c, rows, SUB_ROWS_MAX);
|
||||||
|
CHECK(row_label(rows, n, "蓝牙状态未知") < 0, "真实态已知 → 无未知占位行");
|
||||||
|
|
||||||
|
printf("== P5 M47 点击收敛:定稿 / 回滚 + 可见错误 ==\n");
|
||||||
|
/* 5a 下发成功且真实态已达成 → 立即定稿,无弹窗,settings/状态栏=真实态 */
|
||||||
|
g_bt_state = 1; g_onoff_rc = MEOW_OK; g_modal_n = 0; g_bt_en_setting = 0;
|
||||||
|
memset(&c, 0, sizeof c);
|
||||||
|
unsigned long long t0 = now_ms();
|
||||||
|
bt_top_sw_set(&c, 1);
|
||||||
|
unsigned long long dt = now_ms() - t0;
|
||||||
|
CHECK(g_modal_n == 0, "已达成 → 无错误弹窗 (弹窗数=%d)", g_modal_n);
|
||||||
|
CHECK(g_bt_en_setting == 1, "已达成 → settings 定稿为 1");
|
||||||
|
CHECK(g_sys.bt_on == 1, "已达成 → 状态栏 g_sys.bt_on=1");
|
||||||
|
CHECK(dt < BT_TOGGLE_SETTLE_MS, "已达成 → 不必等满超时 (%llums)", dt);
|
||||||
|
|
||||||
|
/* 5b 下发失败、真实态仍为开(要求关)→ 回滚到开 + 可见弹窗 */
|
||||||
|
g_bt_state = 1; g_onoff_rc = MEOW_ERR_NODEV; g_modal_n = 0; g_bt_en_setting = 1;
|
||||||
|
memset(&c, 0, sizeof c);
|
||||||
|
bt_top_sw_set(&c, 0);
|
||||||
|
CHECK(g_modal_n == 1, "下发失败 → 有可见错误弹窗");
|
||||||
|
CHECK(g_bt_en_setting == 1, "下发失败 → settings 回滚为真实态 1 (实际=%d)", g_bt_en_setting);
|
||||||
|
CHECK(g_sys.bt_on == 1, "下发失败 → 状态栏仍为真实态 1");
|
||||||
|
|
||||||
|
/* 5c 下发成功但真实态不收敛(要求开、真实一直关)→ 等满上限后回滚 + 弹窗 */
|
||||||
|
g_bt_state = 0; g_onoff_rc = MEOW_OK; g_modal_n = 0; g_bt_en_setting = 1;
|
||||||
|
memset(&c, 0, sizeof c);
|
||||||
|
t0 = now_ms();
|
||||||
|
bt_top_sw_set(&c, 1);
|
||||||
|
dt = now_ms() - t0;
|
||||||
|
CHECK(g_modal_n == 1, "超时未收敛 → 有可见错误弹窗(%s)", g_modal_line);
|
||||||
|
CHECK(g_bt_en_setting == 0, "超时未收敛 → settings 回滚为真实态 0");
|
||||||
|
CHECK(g_sys.bt_on == 0, "超时未收敛 → 状态栏=真实态 0");
|
||||||
|
CHECK(dt >= BT_TOGGLE_SETTLE_MS, "确实等满了上限 (%llums)", dt);
|
||||||
|
|
||||||
|
/* 5d 真实态未知 → 等满上限后仍是未知 + 弹窗(绝不把未知显示成成功) */
|
||||||
|
g_bt_state = -1; g_onoff_rc = MEOW_OK; g_modal_n = 0; g_bt_en_setting = 1;
|
||||||
|
memset(&c, 0, sizeof c);
|
||||||
|
bt_top_sw_set(&c, 1);
|
||||||
|
CHECK(g_modal_n == 1 && strstr(g_modal_line, "无法确认") != NULL,
|
||||||
|
"未知 → 弹窗明说无法确认 (%s)", g_modal_line);
|
||||||
|
CHECK(c.bt_on == -1, "未知 → 定稿态仍为 -1 (实际=%d)", c.bt_on);
|
||||||
|
CHECK(g_bt_en_setting == 0, "未知 → settings 不留意图值 1(回滚为 0)");
|
||||||
|
|
||||||
|
printf("== P6 M47 收敛矩阵(真实态 × 下发结果 × 探测 × 超时)==\n");
|
||||||
|
{
|
||||||
|
static const struct {
|
||||||
|
int last, want, ok, probe, to; /* 输入 */
|
||||||
|
int disp, pend, rb, err; /* 期望输出 */
|
||||||
|
const char *what;
|
||||||
|
} m[] = {
|
||||||
|
{ 1, 1, 1, 1, 0, 1, 0, 0, 0, "真开·下发成功·探测开" },
|
||||||
|
{ 1, 0, 1, 0, 0, 0, 0, 0, 0, "真开·下发成功·探测关" },
|
||||||
|
{ 1, 0, 1, 1, 1, 1, 0, 1, 1, "真开·下发成功·未收敛(超时)" },
|
||||||
|
{ 1, 0, 0, 1, 0, 1, 0, 1, 1, "真开·下发失败·探测仍开" },
|
||||||
|
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, "真开·下发失败但探测已关(不误报)" },
|
||||||
|
{ 0, 1, 1, 1, 0, 1, 0, 0, 0, "真关·下发成功·探测开" },
|
||||||
|
{ 0, 1, 1, 0, 1, 0, 0, 1, 1, "真关·下发成功·未收敛(超时)" },
|
||||||
|
{ 0, 1, 0, 0, 0, 0, 0, 1, 1, "真关·下发失败·仍关" },
|
||||||
|
{ 1, 0, 1, -1, 0, 1, 1, 1, 0, "真开·下发成功·探测未知(继续等)" },
|
||||||
|
{ 1, 0, 1, -1, 1, 1, 0, 1, 1, "真开·下发成功·探测未知(超时)" },
|
||||||
|
{-1, 1, 1, -1, 1, -1, 0, 1, 1, "未知·下发成功·探测仍未知(超时)" },
|
||||||
|
{-1, 1, 1, -1, 0, -1, 1, 1, 0, "未知·下发成功·探测仍未知(继续等)" },
|
||||||
|
};
|
||||||
|
for (size_t i = 0; i < sizeof m / sizeof m[0]; i++) {
|
||||||
|
int d = meow_toggle_display(m[i].want, m[i].last, m[i].ok, m[i].probe, m[i].to);
|
||||||
|
int p = meow_toggle_pending(m[i].want, m[i].last, m[i].ok, m[i].probe, m[i].to);
|
||||||
|
int r = meow_toggle_rollback(m[i].want, m[i].last, m[i].ok, m[i].probe, m[i].to);
|
||||||
|
int e = meow_toggle_error(m[i].want, m[i].last, m[i].ok, m[i].probe, m[i].to);
|
||||||
|
CHECK(d == m[i].disp && p == m[i].pend && r == m[i].rb && e == m[i].err,
|
||||||
|
"%s → disp=%d pend=%d rb=%d err=%d (期望 %d/%d/%d/%d)",
|
||||||
|
m[i].what, d, p, r, e, m[i].disp, m[i].pend, m[i].rb, m[i].err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
printf(g_fail ? "\nRESULT: %d FAIL(S)\n" : "\nRESULT: ALL PASS\n", g_fail);
|
printf(g_fail ? "\nRESULT: %d FAIL(S)\n" : "\nRESULT: ALL PASS\n", g_fail);
|
||||||
return g_fail;
|
return g_fail;
|
||||||
}
|
}
|
||||||
@@ -40,8 +40,47 @@ 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_wifi(int on) { (void)on; }
|
||||||
void s_net_toggle_bt(int on) { (void)on; }
|
void s_net_toggle_bt(int on) { (void)on; }
|
||||||
void net_wifi_scan_trigger(void) {}
|
void net_wifi_scan_trigger(void) {}
|
||||||
|
int net_bt_connected(meow_net_t *out, int max) { (void)out; (void)max; return 0; }
|
||||||
|
int net_bt_scan(meow_net_t *out, int max) { (void)out; (void)max; return 0; }
|
||||||
|
void net_bt_scan_trigger(void) {}
|
||||||
|
|
||||||
|
/* UI 桩:wifi_connecting_banner 走 SDK 绘制函数;宿主无 fb,空实现即可(不验证像素)。 */
|
||||||
|
const meow_ui_theme_t *s_ui_theme(void) { static meow_ui_theme_t th; return &th; }
|
||||||
|
int meow_ui_width(void) { return 480; }
|
||||||
|
void meow_ui_fill_rect(int x, int y, int w, int h, uint32_t color)
|
||||||
|
{ (void)x; (void)y; (void)w; (void)h; (void)color; }
|
||||||
|
void meow_ui_text_center(int cx, int y, const char *utf8, int size, uint32_t color)
|
||||||
|
{ (void)cx; (void)y; (void)utf8; (void)size; (void)color; }
|
||||||
|
int meow_ui_text_height(int size) { return size; }
|
||||||
|
void meow_dirty_full(void) {}
|
||||||
|
void meow_ui_flush(void) {}
|
||||||
char *popen_read_tmo(int tmo_ms, const char *cmd)
|
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 */
|
{
|
||||||
|
(void)tmo_ms;
|
||||||
|
/* 直连回退(不可达分支)会跑 wpa_cli 剧本:list_networks/add_network/status/save_config。
|
||||||
|
* 桩按子命令给近似真机的应答——旧桩统一返回 "0\n",会让 nm_direct_connect_ex 等满 10s
|
||||||
|
* 再报 MEOW_ERR_NODEV(该断言因此长期失败;M47 顺手把桩修成可收敛)。 */
|
||||||
|
if (strstr(cmd, " status")) return strdup("wpa_state=COMPLETED\nip_address=192.168.1.5\nssid=Home2G\n");
|
||||||
|
if (strstr(cmd, "list_networks")) return strdup("network id / ssid / bssid / flags\n0\tHome2G\t00:11:22:33:44:01\t[WPA-PSK-CCMP]\n");
|
||||||
|
return strdup("OK\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- M47:net.c 三态直连探测桩(daemon 不可达时 nm_*_power_state 落到这里) ---------- */
|
||||||
|
int nm_bt_power_state(void); /* netmgr_ipc.c(头文件不在本单改动范围) */
|
||||||
|
static int g_bt_probe = -1, g_wifi_probe = -1;
|
||||||
|
int net_bt_power_probe(void) { return g_bt_probe; }
|
||||||
|
int net_wifi_power_probe(void) { return g_wifi_probe; }
|
||||||
|
|
||||||
|
/* M47:开关收敛判定纯函数的实现放在 bt_page.c,矩阵测试在 test_bt_page.c;本测试只验
|
||||||
|
* nm_*_power_state 的解析/回退与 wifi_rows 的未知态渲染,故这里给可链接的最小桩。 */
|
||||||
|
int meow_toggle_display(int intent, int last, int ok, int probe, int to)
|
||||||
|
{ (void)intent; (void)ok; (void)to; return (probe >= 0) ? probe : last; }
|
||||||
|
int meow_toggle_pending(int intent, int last, int ok, int probe, int to)
|
||||||
|
{ (void)intent; (void)last; (void)ok; (void)probe; (void)to; return 0; }
|
||||||
|
int meow_toggle_rollback(int intent, int last, int ok, int probe, int to)
|
||||||
|
{ (void)intent; (void)last; (void)ok; (void)probe; (void)to; return 0; }
|
||||||
|
int meow_toggle_error(int intent, int last, int ok, int probe, int to)
|
||||||
|
{ (void)intent; (void)last; (void)ok; (void)probe; (void)to; return 0; }
|
||||||
|
|
||||||
/* ---------- launcher/UI/IME 桩(wifi_page.c 链接所需;测试不触碰这些路径) ---------- */
|
/* ---------- launcher/UI/IME 桩(wifi_page.c 链接所需;测试不触碰这些路径) ---------- */
|
||||||
static unsigned long long g_test_ms = 0;
|
static unsigned long long g_test_ms = 0;
|
||||||
@@ -175,7 +214,9 @@ static int row_has_label(const sub_row_t *rows, int n, const char *needle)
|
|||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
char sock[110];
|
char sock[110];
|
||||||
snprintf(sock, sizeof sock, "/tmp/meow_net_test_%d.sock", (int)getpid());
|
/* 宿主 /tmp 可能不可写(Android 上 /tmp 属 shell):优先 TMPDIR。 */
|
||||||
|
snprintf(sock, sizeof sock, "%s/meow_net_test_%d.sock",
|
||||||
|
getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp", (int)getpid());
|
||||||
|
|
||||||
fake_net_t f;
|
fake_net_t f;
|
||||||
memset(&f, 0, sizeof f);
|
memset(&f, 0, sizeof f);
|
||||||
@@ -236,7 +277,26 @@ int main(void)
|
|||||||
assert(strstr(err, "add_network") && "connect 失败带回原因");
|
assert(strstr(err, "add_network") && "connect 失败带回原因");
|
||||||
printf("[D] connect 失败: rc=%d err=%s PASS\n", rc, err);
|
printf("[D] connect 失败: rc=%d err=%s PASS\n", rc, err);
|
||||||
|
|
||||||
/* ---- ③ 降级占位:MEOW_NET_SOCK 指向不存在路径 ----
|
/* ---- ③ M47:开关真实态三态查询(daemon 权威解析) ---- */
|
||||||
|
{
|
||||||
|
f.bt_status_text = "bt available=1 powered=1 address=00:1A:7D:DA:71:13 paired=2 connected=0";
|
||||||
|
assert(nm_bt_power_state() == 1 && "bt status powered=1 → 1(开)");
|
||||||
|
f.bt_status_text = "bt available=1 powered=0 address=00:1A:7D:DA:71:13 paired=2 connected=0";
|
||||||
|
assert(nm_bt_power_state() == 0 && "bt status powered=0 → 0(关)");
|
||||||
|
f.bt_status_text = "bt available=0 powered=0 address=- paired=-1 connected=-1";
|
||||||
|
assert(nm_bt_power_state() == 0 && "无控制器(available=0) → 0");
|
||||||
|
f.bt_status_text = "bt available=1 address=00:1A:7D:DA:71:13 paired=-1 connected=-1";
|
||||||
|
assert(nm_bt_power_state() == -1 && "缺 powered 字段 → -1(未知,不猜 0)");
|
||||||
|
g_wifi_probe = -1;
|
||||||
|
assert(nm_wifi_power_state() == -1 && "wifi 探测失败 → -1(不再默认已开)");
|
||||||
|
g_wifi_probe = 1;
|
||||||
|
assert(nm_wifi_power_state() == 1 && "wifi 探测开 → 1");
|
||||||
|
g_wifi_probe = 0;
|
||||||
|
assert(nm_wifi_power_state() == 0 && "wifi 探测关 → 0");
|
||||||
|
printf("[F] nm_bt_power_state/nm_wifi_power_state 三态解析 PASS\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- ④ 降级占位:MEOW_NET_SOCK 指向不存在路径 ----
|
||||||
* 先发一次 netmgr_wifi(内部强制重连,失败 → 置可达缓存=0),再断言 netmgr_ok()。 */
|
* 先发一次 netmgr_wifi(内部强制重连,失败 → 置可达缓存=0),再断言 netmgr_ok()。 */
|
||||||
setenv("MEOW_NET_SOCK", "/tmp/meow_net_nonexistent_zz.sock", 1);
|
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_wifi("status", NULL, err, sizeof err) == NETMGR_UNREACH && "netmgr_wifi=NETMGR_UNREACH");
|
||||||
@@ -254,6 +314,21 @@ int main(void)
|
|||||||
printf("[E] 降级: netmgr_ok=0, UNREACH, 回退不崩, 占位渲染 PASS\n");
|
printf("[E] 降级: netmgr_ok=0, UNREACH, 回退不崩, 占位渲染 PASS\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- ⑤ M47:daemon 不可达时真实态回退直连探测;探测失败=未知(不当已关/已开) ---- */
|
||||||
|
g_bt_probe = -1;
|
||||||
|
assert(nm_bt_power_state() == -1 && "BT daemon 不可达 + 直连探测失败 → -1");
|
||||||
|
g_bt_probe = 0;
|
||||||
|
assert(nm_bt_power_state() == 0 && "BT daemon 不可达 + 直连探测关 → 0");
|
||||||
|
g_wifi_probe = -1;
|
||||||
|
assert(nm_wifi_power_state() == -1 && "WiFi 探测失败 → -1");
|
||||||
|
{
|
||||||
|
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, "WiFi 开关状态未知") && "未知态 → 行内第三态占位");
|
||||||
|
printf("[G] 不可达回退三态: BT/WiFi 失败=未知, 行内第三态 PASS\n");
|
||||||
|
}
|
||||||
|
|
||||||
printf("ALL PASS\n");
|
printf("ALL PASS\n");
|
||||||
unlink(sock);
|
unlink(sock);
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -11,6 +11,11 @@
|
|||||||
* 【M6.0 迁移】全部查询/操作走 netmgr IPC(netmgr_ipc.c 的 nm_* 封装),netmgrd 是 WiFi 权威;
|
* 【M6.0 迁移】全部查询/操作走 netmgr IPC(netmgr_ipc.c 的 nm_* 封装),netmgrd 是 WiFi 权威;
|
||||||
* netmgrd 不可达(NETMGR_UNREACH)→ 自动回退 net.c 直连 wpa_cli(双轨保功能),页面显示降级占位
|
* netmgrd 不可达(NETMGR_UNREACH)→ 自动回退 net.c 直连 wpa_cli(双轨保功能),页面显示降级占位
|
||||||
* 「网络管理服务未运行(直连模式)」。
|
* 「网络管理服务未运行(直连模式)」。
|
||||||
|
*
|
||||||
|
* 【M47 开关可信化】顶栏开关显示态只来自真实态三态查询 nm_wifi_power_state()(rfkill wlan
|
||||||
|
* soft/hard,回退 wlan0 IFF_UP;探测失败=未知),**读不到不再默认「已开」**;settings 的
|
||||||
|
* network.wifi_enabled 只承载用户意图。点击先出示「正在开启/关闭…(等待确认)」,下发 → 有界
|
||||||
|
* 轮询真实态 → 真实态定稿;失败/超时回滚并弹窗(不许乐观显示成功)。
|
||||||
*/
|
*/
|
||||||
#include "meow_launcher_internal.h"
|
#include "meow_launcher_internal.h"
|
||||||
|
|
||||||
@@ -27,32 +32,84 @@ typedef struct {
|
|||||||
int saved_n, scan_n;
|
int saved_n, scan_n;
|
||||||
char ssid[64];
|
char ssid[64];
|
||||||
int connected; /* nm_wifi_connected()(IPC>直连回退 wlan_connected) */
|
int connected; /* nm_wifi_connected()(IPC>直连回退 wlan_connected) */
|
||||||
int wifi_on; /* network.wifi_enabled */
|
int sw_state; /* 真实射频态三态:-1 未知 / 0 关 / 1 开(M47) */
|
||||||
|
unsigned long long sw_ts; /* 上面这次查询的时刻(缓存去重) */
|
||||||
long last_scan_ms; /* 扫描触发节流 */
|
long last_scan_ms; /* 扫描触发节流 */
|
||||||
} wifi_ctx_t;
|
} wifi_ctx_t;
|
||||||
|
|
||||||
|
/* M47:开关收敛参数(宿主测试可预先定义改小)。WiFi 走 wifi_up.sh(起 wpa_supplicant +
|
||||||
|
* 等关联/DHCP),比 BT 慢,所以上限 5s;250ms 探一次(≤20 次),不 20ms 级忙轮询。 */
|
||||||
|
#ifndef WF_TOGGLE_SETTLE_MS
|
||||||
|
#define WF_TOGGLE_SETTLE_MS 5000
|
||||||
|
#endif
|
||||||
|
#ifndef WF_TOGGLE_POLL_MS
|
||||||
|
#define WF_TOGGLE_POLL_MS 250
|
||||||
|
#endif
|
||||||
|
#define WF_STATE_TTL_MS 800
|
||||||
|
|
||||||
/* 顶栏开关当前值 */
|
/* 真实态查询(带缓存)。定义见 netmgr_ipc.c:netmgrd 不报射频态,故取 rfkill/wlan0。 */
|
||||||
|
extern int nm_wifi_power_state(void);
|
||||||
|
/* 收敛判定纯函数,实现见 bt_page.c(两个页面共用一套;矩阵测试在 test_bt_page.c) */
|
||||||
|
extern int meow_toggle_display(int intent, int last_real, int dispatch_ok, int probe, int timed_out);
|
||||||
|
extern int meow_toggle_pending(int intent, int last_real, int dispatch_ok, int probe, int timed_out);
|
||||||
|
extern int meow_toggle_rollback(int intent, int last_real, int dispatch_ok, int probe, int timed_out);
|
||||||
|
extern int meow_toggle_error(int intent, int last_real, int dispatch_ok, int probe, int timed_out);
|
||||||
|
|
||||||
|
static int wifi_state_now(wifi_ctx_t *c, int force)
|
||||||
|
{
|
||||||
|
unsigned long long t = now_ms();
|
||||||
|
if (!force && c->sw_ts && t - c->sw_ts < WF_STATE_TTL_MS) return c->sw_state;
|
||||||
|
c->sw_state = nm_wifi_power_state();
|
||||||
|
c->sw_ts = t;
|
||||||
|
return c->sw_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 顶栏开关当前值:只报真实态(-1=未知 → subpage 画「?」)。绝不再「读不到就默认 1」。 */
|
||||||
static int wifi_top_sw(void *ctx, int *on)
|
static int wifi_top_sw(void *ctx, int *on)
|
||||||
{
|
{
|
||||||
(void)ctx;
|
wifi_ctx_t *c = ctx;
|
||||||
int v = 1;
|
*on = wifi_state_now(c, 0);
|
||||||
if (meow_settings_get_bool("network.wifi_enabled", &v) != 0) v = 1;
|
|
||||||
*on = v;
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* 顶栏开关切换:写设置 + 开/关网卡(netmgr on/off,回退直连)+ 触发重扫 + 同步状态栏 */
|
/* 顶栏开关切换:落盘意图 → 开/关网卡(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);
|
int want = on ? 1 : 0;
|
||||||
nm_wifi_toggle(on);
|
int last_real = wifi_state_now(c, 0);
|
||||||
g_sys.wifi_on = on;
|
meow_settings_set_bool("network.wifi_enabled", want); /* 意图持久化 */
|
||||||
paint_sb_right(); /* 状态栏 WiFi 图标随开关即时刷新 */
|
|
||||||
meow_settings_save();
|
meow_settings_save();
|
||||||
if (on) { nm_wifi_scan_trigger(); c->last_scan_ms = now_ms(); }
|
int dispatch_ok = (nm_wifi_toggle(want) == MEOW_OK);
|
||||||
|
unsigned long long deadline = now_ms() + WF_TOGGLE_SETTLE_MS;
|
||||||
|
int probe = wifi_state_now(c, 1), timed_out = 0;
|
||||||
|
while (meow_toggle_pending(want, last_real, dispatch_ok, probe, timed_out)) {
|
||||||
|
if (now_ms() >= deadline) { timed_out = 1; break; }
|
||||||
|
usleep(WF_TOGGLE_POLL_MS * 1000);
|
||||||
|
probe = wifi_state_now(c, 1);
|
||||||
|
}
|
||||||
|
int disp = meow_toggle_display(want, last_real, dispatch_ok, probe, timed_out);
|
||||||
|
int rb = meow_toggle_rollback(want, last_real, dispatch_ok, probe, timed_out);
|
||||||
|
int err = meow_toggle_error(want, last_real, dispatch_ok, probe, timed_out);
|
||||||
|
c->sw_state = disp; c->sw_ts = now_ms();
|
||||||
|
meow_settings_set_bool("network.wifi_enabled", disp == 1); /* 只留已确认的真实态 */
|
||||||
|
meow_settings_save();
|
||||||
|
if (disp >= 0) g_sys.wifi_on = disp;
|
||||||
|
paint_sb_right(); /* 状态栏 WiFi 图标随「已确认」状态刷新 */
|
||||||
|
if (disp == 1) { nm_wifi_scan_trigger(); c->last_scan_ms = now_ms(); }
|
||||||
|
logmsg("wifi_page: toggle want=%d ok=%d probe=%d to=%d disp=%d rollback=%d err=%d",
|
||||||
|
want, dispatch_ok, probe, timed_out, disp, rb, err);
|
||||||
|
if (err) {
|
||||||
|
char line[96];
|
||||||
|
const char *txt[1]; txt[0] = line;
|
||||||
|
if (disp < 0) snprintf(line, sizeof line, "无法确认 WiFi 状态(射频/服务无响应)");
|
||||||
|
else snprintf(line, sizeof line, "切换未生效:当前仍为%s(%dms 内未确认)",
|
||||||
|
disp ? "开启" : "关闭", WF_TOGGLE_SETTLE_MS);
|
||||||
|
s_dd_modal("WiFi", txt, 1, -1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -72,6 +129,7 @@ static void wifi_scan(void *ctx)
|
|||||||
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;
|
||||||
|
c->sw_state = wifi_state_now(c, 0); /* 真实射频态(三态);未知时下面显式说明 */
|
||||||
c->connected = nm_wifi_connected();
|
c->connected = nm_wifi_connected();
|
||||||
if (c->connected) {
|
if (c->connected) {
|
||||||
nm_wifi_ssid(c->ssid, sizeof c->ssid);
|
nm_wifi_ssid(c->ssid, sizeof c->ssid);
|
||||||
@@ -90,6 +148,12 @@ static int wifi_rows(void *ctx, sub_row_t *rbuf, int max)
|
|||||||
rbuf[n].kind = SUB_HDR; rbuf[n].label = "网络管理服务未运行(直连模式)";
|
rbuf[n].kind = SUB_HDR; rbuf[n].label = "网络管理服务未运行(直连模式)";
|
||||||
rbuf[n].sub = NULL; rbuf[n].on = 0; rbuf[n].act = 0; n++;
|
rbuf[n].sub = NULL; rbuf[n].on = 0; rbuf[n].act = 0; n++;
|
||||||
}
|
}
|
||||||
|
/* 第三态显式呈现(M47):射频态未知 ≠ 已关;顶栏开关同时画「?」,这里补文字说明。 */
|
||||||
|
if (c->sw_state < 0 && n < max) {
|
||||||
|
rbuf[n].kind = SUB_ROW; rbuf[n].label = "WiFi 开关状态未知";
|
||||||
|
rbuf[n].sub = "无法读取射频状态(rfkill/网卡无响应)";
|
||||||
|
rbuf[n].on = 0; rbuf[n].act = -2; 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) {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
# ---- 用户态/服务守护二进制(usr/bin,全部 chmod 755 静态 MIPS)----
|
# ---- 用户态/服务守护二进制(usr/bin,全部 chmod 755 静态 MIPS)----
|
||||||
patch_v2/bootmenu/meow_bootmenu|/usr/bin/meow_bootmenu|4441822d87e76bfce2ce584604ac3830|引导选单 M6.25(开机菜单/第二屏亮度按用户设置恢复:显示前读 settings.conf 写回,方案A)
|
patch_v2/bootmenu/meow_bootmenu|/usr/bin/meow_bootmenu|4441822d87e76bfce2ce584604ac3830|引导选单 M6.25(开机菜单/第二屏亮度按用户设置恢复:显示前读 settings.conf 写回,方案A)
|
||||||
patch_v2/meow_launcherd/meow_launcherd|/usr/bin/meow_launcherd|605e13a9d154d179f4131e0c5e85c90f|M40 电源键/锁屏链修复(App 前台电源菜单补齐 §10.5:notif 全抑制+帧恢复+SIGCONT 排空+标记翻回,关闭后不再停留菜单画面;show_power_menu 恢复进入前背景帧+重绘挪到首轮按键采样后;activity 标记排空窗+会话入口清理+manual_off 去单向闩锁;锁屏电源改按下边沿+进场 300ms,熄屏退出也恢复底层帧;SIGCONT 前写 /tmp/meow_app_repaint), 链 meow_ui.a 86800633;继承 M39 点击输入门(M39 触摸守卫/MENU_TOUCH_GUARD_MS 150ms + SDK 启动排空);继承 M6.46 杀后台修复+SDK 时钟 ABI 修复(M11/M40);0warning
|
patch_v2/meow_launcherd/meow_launcherd|/usr/bin/meow_launcherd|1c88661a9410ac1a4139304362281cdd|M47 设置页蓝牙/WiFi 开关真实态收口(UI 只认三态真实态:-1 未知/0 关/1 开;读不到不再默认已开/已关;点击→「正在…等待设备确认」→有界轮询 250ms/4~5s 收敛→真实态定稿,失败/超时回滚+弹窗;settings 降级为意图持久化;net.c:蓝牙直连补 rfkill soft(与 netmgrd 同一语义)、WiFi 同步判据改射频态且未知不写;顶栏开关未知态画「?」;subpage live 回读真实态), 链 meow_ui.a 86800633;继承 M40 电源键/锁屏链修复;继承 M39 点击输入门(M39 触摸守卫/MENU_TOUCH_GUARD_MS 150ms + SDK 启动排空);继承 M6.46 杀后台修复+SDK 时钟 ABI 修复(M11/M40);0warning
|
||||||
patch_v2/meow_watchdog/meow_watchdog|/usr/bin/meow_watchdog|37c57e0feb9cd220978bfd5deff75a1f|应用看门狗 M40:o32 time32 取时/睡眠(修 held 天文数字→单击误判长按)、电源阈值读 power.longpress_menu_ms/forceoff_ms(缺省 3000/8000,与 launcher 主屏统一);继承 M20 亮屏亮度按 settings 恢复(四支决策不再回退固定 60)
|
patch_v2/meow_watchdog/meow_watchdog|/usr/bin/meow_watchdog|37c57e0feb9cd220978bfd5deff75a1f|应用看门狗 M40:o32 time32 取时/睡眠(修 held 天文数字→单击误判长按)、电源阈值读 power.longpress_menu_ms/forceoff_ms(缺省 3000/8000,与 launcher 主屏统一);继承 M20 亮屏亮度按 settings 恢复(四支决策不再回退固定 60)
|
||||||
patch_v2/meow_svcd/meow_svcd|/usr/bin/meow_svcd|605646c4cc93f2baf29071ea6bc870ea|服务管理器 M2.77(+崩溃上报 meow_crash agent-187)
|
patch_v2/meow_svcd/meow_svcd|/usr/bin/meow_svcd|605646c4cc93f2baf29071ea6bc870ea|服务管理器 M2.77(+崩溃上报 meow_crash agent-187)
|
||||||
mips-repo/bins/meow_mdns/bin/meow_mdns|/usr/bin/meow_mdns|d6b0151bda26768f19952dae1342610d|mDNS 应答器 M2.78(启动竞态+换IP重检 agent-194)
|
mips-repo/bins/meow_mdns/bin/meow_mdns|/usr/bin/meow_mdns|d6b0151bda26768f19952dae1342610d|mDNS 应答器 M2.78(启动竞态+换IP重检 agent-194)
|
||||||
|
|||||||
Reference in new issue
Block a user