diff --git a/patch_v2/meow_launcherd/bt_page.c b/patch_v2/meow_launcherd/bt_page.c index 41a3cf2..0e8a042 100644 --- a/patch_v2/meow_launcherd/bt_page.c +++ b/patch_v2/meow_launcherd/bt_page.c @@ -64,8 +64,13 @@ typedef struct { extern int nm_bt_power_state(void); /* netmgr_ipc.c:daemon 权威 → 直连探测,三态 */ /* 开关收敛参数(宿主测试可在 include 前改小,见 test_bt_page.c)。 - * 依据:本机慢——hciconfig 一次 0.3~2s,netmgrd 的 on/off 自身还带 ~1.5s 等待; - * 250ms 间隔在 4s 内最多探 16 次,既不 20ms 级忙轮询,又能在 1s 内看到首次收敛。 */ + * 上限从**点击时刻**起算(评审 r1 P2-3),故它约束的是「从点击到出结论」的总时长: + * - 快路径(daemon 正常,`bt status` 本地 IPC 几 ms):4s 内约 16 次探测,通常 1~2s 收敛; + * - 慢路径(netmgrd 卡住:on/off 读超时 1.5s → 直连 s_net_toggle_bt 的 hciconfig 0.3~2s; + * 之后一次探测 = status 1.5s 超时 + hciconfig 兜底 1.8s ≈ 3.3s):总阻塞最坏 + * ≈3.5s(下发) + 3.3s(最后一次探测) ≈ **7s**,预算内只够 1 次探测。此时能确认的只是 + * 「探测超时」,**不能**宣称「设备无响应」——弹窗文案按此区分(「无法确认当前状态」)。 + * 250ms 只是快路径的轮询间隔(不 20ms 级忙轮询);慢路径由探测自身耗时决定。 */ #ifndef BT_TOGGLE_SETTLE_MS #define BT_TOGGLE_SETTLE_MS 4000 #endif @@ -123,20 +128,23 @@ static const char *bt_conn_name(const bt_ctx_t *c, const char *mac) * dispatch_ok(下发命令是否成功)、probe(有界等待后的探测结果,-1 未知)、 * timed_out(是否已到等待上限)。 * 输出(四个访问器共用同一套判定,拆开只为调用处可读): - * meow_toggle_display() 应显示的态(-1 未知 / 0 / 1):有 probe 只认 probe;probe 未知才回退 - * last_real(上一次「已知」值),两者都未知 → -1。 + * meow_toggle_display() 应显示的态(-1 未知 / 0 / 1):**只认 probe**。probe 未知 → -1, + * 不许回退 last_real 冒充确定态(评审 r1 P2-2:从没被确认过的旧值 + * 一旦显示成确定态,就与本单「探测失败=未知」的契约自相矛盾)。 * meow_toggle_pending() 1=还要继续等:下发成功、未超时、且 probe 未等于意图。 * 注意「probe 未知」也算没收敛——绝不因为 last_real 恰好等于意图就收工。 - * meow_toggle_rollback() 1=意图未达成(显示态 != 意图)→ UI/settings 必须回滚到真实态。 + * meow_toggle_rollback() 1=意图未达成(显示态 != 意图)→ UI 必须回滚到真实态。 * meow_toggle_error() 1=收尾后仍未确认 → 必须给用户可见的错误提示(不许静默失败)。 + * last_real 不参与状态判定:它只由调用方用于弹窗文案(「上次已知为开启,当前无法确认」), + * 以及后续异步化时判断「回滚到哪」;保留在接口里是因为它是本单约定的输入维度。 * 判定矩阵见 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 d = (probe >= 0) ? (probe ? 1 : 0) : -1; /* 未确认 = 未知,绝不拿旧值顶替 */ int confirmed = (probe >= 0) && ((probe ? 1 : 0) == want); + (void)last_real; /* 仅弹窗文案用,见函数头注释 */ *disp = d; *pending = (dispatch_ok && !timed_out && !confirmed) ? 1 : 0; *rollback = (d == want) ? 0 : 1; @@ -192,12 +200,13 @@ static int bt_top_sw(void *ctx, int *on) static void bt_top_sw_set(void *ctx, int on) { bt_ctx_t *c = ctx; + unsigned long long t_click = now_ms(); /* 上限从点击起算(含下发耗时,见参数注释) */ int want = on ? 1 : 0; int last_real = bt_state_now(c, 0); meow_settings_set_bool("network.bluetooth_enabled", want); /* 意图持久化 */ meow_settings_save(); int dispatch_ok = (nm_bt_onoff(want) == MEOW_OK); - unsigned long long deadline = now_ms() + BT_TOGGLE_SETTLE_MS; + unsigned long long deadline = t_click + 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; } @@ -207,22 +216,32 @@ static void bt_top_sw_set(void *ctx, int on) 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); + unsigned long long elapsed = now_ms() - t_click; c->bt_on = disp; c->bt_on_ts = now_ms(); - /* 回滚语义:settings 只留「已确认的真实态」——失败/超时不留一个骗人的意图值 */ - meow_settings_set_bool("network.bluetooth_enabled", disp == 1); - meow_settings_save(); + /* 回滚语义:只在拿到**真实态**时才改写 settings(确认态或确认的相反态)。 + * disp == -1(探测未知)时保留用户原意图不动——本键被 notif_center / settings_page 当 + * 显示态读、被 app_launcher 用来决定自动拉起 WiFi,写 0 会把「不知道」污染成「确定已关」 + * (评审 r1 P2-1)。 */ + if (disp >= 0) { + 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); + logmsg("bt_page: toggle want=%d ok=%d probe=%d to=%d disp=%d rollback=%d err=%d elapsed=%llums", + want, dispatch_ok, probe, timed_out, disp, rb, err, elapsed); if (err) { - char line[96]; + char line[128]; 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); + /* 文案必须与结论同义:probe 未知 = 「无法确认」(不冒充未生效/未响应); + * probe 已知且 != 意图 = 确认未生效。last_real 只在这里出现。 */ + if (disp < 0) + snprintf(line, sizeof line, "无法确认当前状态(上次已知:%s)", + last_real < 0 ? "未知" : (last_real ? "开启" : "关闭")); + else + snprintf(line, sizeof line, "切换未生效:当前仍为%s", disp ? "开启" : "关闭"); s_dd_modal("蓝牙", txt, 1, -1); } } diff --git a/patch_v2/meow_launcherd/netmgr_ipc.c b/patch_v2/meow_launcherd/netmgr_ipc.c index 671ad54..c0edf16 100644 --- a/patch_v2/meow_launcherd/netmgr_ipc.c +++ b/patch_v2/meow_launcherd/netmgr_ipc.c @@ -101,9 +101,14 @@ static int nm_hello(int fd) } /* 发一条 WIFI_CTL/BT_CTL 子命令,收 RESULT。返回 netmgrd 业务 code;NETMGR_UNREACH=不可达。 - * tmo_ms:读超时(connect/connecth/scan 需覆盖 netmgrd 同步等待,见 NM_TMO_*)。 */ -static int nm_cmd(uint16_t ctl_type, uint16_t res_type, const char *cmd, const char *arg, - char *text, int tlen, int tmo_ms) + * tmo_ms:读超时(connect/connecth/scan 需覆盖 netmgrd 同步等待,见 NM_TMO_*)。 + * read_timeout(可空):非空时,「帧收发失败/类型不符」这一支只回写 *read_timeout=1 且**不** + * 把可达性缓存打成 down——调用方据此区分「读超时」与「真不可达」。M47 的 BT 状态查询用它: + * netmgrd 单线程忙时(bt status 自己会跑 hciconfig)状态查询可能超过 1.5s,若据此标 down, + * 紧随的 bt_rows 会闪出「服务未运行(直连模式)」并把后续探测切到 1.8s 的 hciconfig + * (评审 r1 观察;global 超时常量不动,WiFi/DNS 路径行为不变)。 */ +static int nm_cmd_t(uint16_t ctl_type, uint16_t res_type, const char *cmd, const char *arg, + char *text, int tlen, int tmo_ms, int *read_timeout) { m_net_cmd_t q; memset(&q, 0, sizeof q); @@ -115,7 +120,10 @@ static int nm_cmd(uint16_t ctl_type, uint16_t res_type, const char *cmd, const c 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, tmo_ms) < 0 || rtype != res_type) { - close(fd); nm_cache(0); return NETMGR_UNREACH; + close(fd); + if (read_timeout) *read_timeout = 1; + else nm_cache(0); + return NETMGR_UNREACH; } m_net_cmd_result_t res; memcpy(&res, rbuf, sizeof res); close(fd); @@ -126,6 +134,12 @@ static int nm_cmd(uint16_t ctl_type, uint16_t res_type, const char *cmd, const c return (int)res.code; } +static int nm_cmd(uint16_t ctl_type, uint16_t res_type, const char *cmd, const char *arg, + char *text, int tlen, int tmo_ms) +{ + return nm_cmd_t(ctl_type, res_type, cmd, arg, text, tlen, tmo_ms, NULL); +} + int netmgr_wifi_tmo(const char *cmd, const char *arg, char *text, int tlen, int tmo_ms) { return nm_cmd(MSG_NET_WIFI_CTL, MSG_NET_WIFI_RESULT, cmd, arg, text, tlen, tmo_ms); } @@ -563,7 +577,11 @@ int nm_bt_powered(void) int nm_bt_power_state(void) { char text[192]; - int rc = netmgr_bt("status", NULL, text, sizeof text); + int read_timeout = 0; + /* 状态查询走专用入口:读超时 = 「状态未知」,且不把 netmgrd 标成 down(见 nm_cmd_t 注释)。 */ + int rc = nm_cmd_t(MSG_NET_BT_CTL, MSG_NET_BT_RESULT, "status", NULL, + text, sizeof text, NM_TMO_DFL, &read_timeout); + if (read_timeout) return -1; if (rc == NETMGR_UNREACH) return net_bt_power_probe(); /* 直连回退;失败=未知 */ if (rc != MEOW_OK) return -1; /* daemon 报错:不知道就是不知道 */ char v[8]; diff --git a/patch_v2/meow_launcherd/subpage.c b/patch_v2/meow_launcherd/subpage.c index 80f5f78..9080c94 100644 --- a/patch_v2/meow_launcherd/subpage.c +++ b/patch_v2/meow_launcherd/subpage.c @@ -1024,8 +1024,29 @@ int meow_subpage_run(sub_page_t *p) sp_top_sw_toggle(); last_act = now_ms(); /* 切换内可能阻塞数秒:取新鲜时刻, * 否则空闲息屏会按陈旧时刻提前触发 */ + /* 评审 r1 P2-4:M47 起顶开关路径也会开 s_dd_modal(切换失败/ + * 无法确认)。s_dd_modal 是全屏压暗层,关闭后必须像行命中分支 + * 一样排空触摸、复位触摸/按键基线并置 after_modal —— 否则 + * 返回脏区不含状态栏(0..27),会残留压暗到下一次时钟重绘, + * 电源/播放键也可能带进弹窗里的陈旧边沿。 */ + if (tfd >= 0) { + unsigned char e[16]; + while (read(tfd, e, sizeof e) == 16) {} + usleep(40000); + while (read(tfd, e, sizeof e) == 16) {} + } + td = 0; td_ok = 0; scroll_drag = 0; + drag_last_y = 0; + tdx = tdy = tlx = tly = -1; + touch_cooldown_until = now_ms() + 300; + key_router_rebase(); + memset(&kd_pwr, 0, sizeof kd_pwr); + memset(&kd_play, 0, sizeof kd_play); + kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL); + m_prev_pwr = kp_is_down(&kp_power, KEY_POWER); + m_prev_play = kp_is_down(&kp_keys, KEY_PLAYPAUSE); + after_modal = 1; need_paint = 1; - if (tfd >= 0) { unsigned char e[16]; while (read(tfd, e, sizeof e) == 16) {} } continue; } /* 列表行命中(SDK 命中:含滚动换算 + 越界/禁用行不响应) */ diff --git a/patch_v2/meow_launcherd/test_bt_page.c b/patch_v2/meow_launcherd/test_bt_page.c index 04e5045..c3098db 100644 --- a/patch_v2/meow_launcherd/test_bt_page.c +++ b/patch_v2/meow_launcherd/test_bt_page.c @@ -312,14 +312,16 @@ int main(void) CHECK(g_sys.bt_on == 0, "超时未收敛 → 状态栏=真实态 0"); CHECK(dt >= BT_TOGGLE_SETTLE_MS, "确实等满了上限 (%llums)", dt); - /* 5d 真实态未知 → 等满上限后仍是未知 + 弹窗(绝不把未知显示成成功) */ + /* 5d 真实态未知 → 等满上限后仍是未知 + 弹窗(绝不把未知显示成成功); + * settings 保留原意图不动(评审 r1 P2-1:写 0 会把「不知道」污染成「确定已关」, + * notif_center/settings_page/app_launcher 都把这个键当显示态/自动拉起依据读)。 */ 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)"); + CHECK(g_bt_en_setting == 1, "未知 → settings 保留原意图 1,不被改写 (实际=%d)", g_bt_en_setting); printf("== P6 M47 收敛矩阵(真实态 × 下发结果 × 探测 × 超时)==\n"); { @@ -332,12 +334,13 @@ int main(void) { 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, -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, 0, 1, -1, 0, -1, 1, 1, 0, "真开·下发成功·探测未知(继续等)" }, + { 1, 0, 1, -1, 1, -1, 0, 1, 1, "真开·下发成功·探测未知(超时)→未知,不用 last_real 顶替" }, {-1, 1, 1, -1, 1, -1, 0, 1, 1, "未知·下发成功·探测仍未知(超时)" }, {-1, 1, 1, -1, 0, -1, 1, 1, 0, "未知·下发成功·探测仍未知(继续等)" }, }; diff --git a/patch_v2/meow_launcherd/wifi_page.c b/patch_v2/meow_launcherd/wifi_page.c index fe85ac2..a17c418 100644 --- a/patch_v2/meow_launcherd/wifi_page.c +++ b/patch_v2/meow_launcherd/wifi_page.c @@ -38,7 +38,11 @@ typedef struct { } wifi_ctx_t; /* M47:开关收敛参数(宿主测试可预先定义改小)。WiFi 走 wifi_up.sh(起 wpa_supplicant + - * 等关联/DHCP),比 BT 慢,所以上限 5s;250ms 探一次(≤20 次),不 20ms 级忙轮询。 */ + * 等关联/DHCP),比 BT 慢,所以上限 5s——且从**点击时刻**起算(含下发耗时,评审 r1 P2-3)。 + * 快路径(rfkill 写入在 netmgrd 内是毫秒级)5s 内约 20 次探测;慢路径(netmgrd 卡住 → + * on/off 读超时 1.5s → 直连 wifi_up.sh 自身后台化)下探测只剩 1~2 次,此时只能确认 + * 「探测超时」,不能宣称「射频无响应」——弹窗文案按此区分。 + * 250ms 只是快路径的轮询间隔,不 20ms 级忙轮询。 */ #ifndef WF_TOGGLE_SETTLE_MS #define WF_TOGGLE_SETTLE_MS 5000 #endif @@ -79,12 +83,13 @@ static int wifi_top_sw(void *ctx, int *on) static void wifi_top_sw_set(void *ctx, int on) { wifi_ctx_t *c = ctx; + unsigned long long t_click = now_ms(); /* 上限从点击起算(含下发耗时,见参数注释) */ int want = on ? 1 : 0; int last_real = wifi_state_now(c, 0); meow_settings_set_bool("network.wifi_enabled", want); /* 意图持久化 */ meow_settings_save(); int dispatch_ok = (nm_wifi_toggle(want) == MEOW_OK); - unsigned long long deadline = now_ms() + WF_TOGGLE_SETTLE_MS; + unsigned long long deadline = t_click + 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; } @@ -94,20 +99,27 @@ static void wifi_top_sw_set(void *ctx, int on) 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); + unsigned long long elapsed = now_ms() - t_click; c->sw_state = disp; c->sw_ts = now_ms(); - meow_settings_set_bool("network.wifi_enabled", disp == 1); /* 只留已确认的真实态 */ - meow_settings_save(); + /* 只在拿到真实态时改写 settings;disp == -1(探测未知)保留原意图不动——该键被 + * notif_center/settings_page 当显示态读,写 0 会把「不知道」污染成「确定已关」(P2-1)。 */ + if (disp >= 0) { + 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); + logmsg("wifi_page: toggle want=%d ok=%d probe=%d to=%d disp=%d rollback=%d err=%d elapsed=%llums", + want, dispatch_ok, probe, timed_out, disp, rb, err, elapsed); if (err) { - char line[96]; + char line[128]; 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); + if (disp < 0) + snprintf(line, sizeof line, "无法确认当前状态(上次已知:%s)", + last_real < 0 ? "未知" : (last_real ? "开启" : "关闭")); + else + snprintf(line, sizeof line, "切换未生效:当前仍为%s", disp ? "开启" : "关闭"); s_dd_modal("WiFi", txt, 1, -1); } }