fix(§10.5): 通知中心叠加媒体保活——播放中 App 不 SIGSTOP(修播放中滑通知中心音乐停)

- launcher: run_app_notif_overlay 入口 meow_media_app_playing() 判定(当前源+PLAYING+app_id 匹配)
  → 不 SIGSTOP/不 SIGCONT,App 续跑保 PCM;非播放中维持 SIGSTOP 模板;关闭时 unlink 标记挪到
  恢复帧上屏后(防未冻结 App 抢 present 一帧回闪)。
- SDK gfx: present() 见 /tmp/meow_notif_active(env MEOW_NOTIF_ACTIVE_FILE 覆写)自抑上屏,
  仅 App 进程(系统 UI/launcher 不抑制),200ms 节流 stat,脏区累积解除后一次上屏;
  meow_gfx_overlay_suppressed_count 供单测断言。
- SDK input: 叠加期排空丢弃本进程 evdev 副本 + EVIOCGKEY 位图静默对齐(防面板点击被 App
  双重消费/解除后边沿误判),进入沿复位触摸状态机。
- 宿主:新 test_overlay_suppress T1-T4 + test_media §8(8 用例)全 PASS;test_notif_*/
  test_sdk_*/test_vol_ramp_jack 等回归全过;zig-mipsel -Wall -Wextra 全 TU 0 warning。
- meow_launcherd fbb82036(/usr/bin 只读待固件,fw_content.conf 已更)/ meow_ui.a d6242fc2 /
  hello a073535a / files 2b799d07(已热更到卡);ncm 未重链(ncm_app 他 agent 在改)。
This commit is contained in:
MeowOS committed 2026-09-01 15:23:32 +08:00
1 parent 820d1e52bf
commit ad9778476a
19 files changed
+254 -13

No files matched your search

+37
View File
@@ -42,6 +42,11 @@
#define MUI_DEFAULT_W 480
#define MUI_DEFAULT_H 720
/* §10.5 叠加让位标记:与 launcher meow_launcher_internal.h NOTIF_ACTIVE 同路径。
* 通知中心叠加会话期存在;宿主/单测可经 MEOW_NOTIF_ACTIVE_FILE 覆写路径(进程内只解析一次)。 */
#define MEOW_NOTIF_ACTIVE_DEFAULT "/tmp/meow_notif_active"
#define MEOW_OVERLAY_CHK_MS 200 /* 存在性检查节流:别每帧 stat */
/* 脏矩形上限(超出合并为全屏) */
#define MUI_DIRTY_MAX 12
@@ -939,10 +944,42 @@ static int statusbar_touched(void)
return 0;
}
/* ---- §10.5 叠加让位自抑(present 层)----
* /tmp/meow_notif_active 存在 = 通知中心叠加会话进行中(launcher 正在 fb 上渲染面板)。
* App 进程(非系统 UI 渲染者)此时不上屏;200ms 节流缓存存在性(别每帧 stat)。
* 计数 g_overlay_suppressed 供宿主单测断言(meow_gfx_overlay_suppressed_count)。 */
static unsigned long long g_overlay_suppressed = 0;
static int overlay_present_suppressed(void)
{
if (meow_ui_sys_render_enabled()) return 0; /* launcher(系统 UI)渲染面板,照常上屏 */
static char s_path[108];
static int s_path_init = 0;
static unsigned long long s_last_chk = 0;
static int s_last_res = 0;
if (!s_path_init) {
const char *e = getenv("MEOW_NOTIF_ACTIVE_FILE");
snprintf(s_path, sizeof s_path, "%s", (e && e[0]) ? e : MEOW_NOTIF_ACTIVE_DEFAULT);
s_path_init = 1;
}
unsigned long long now = now_ms();
if (s_last_chk && now - s_last_chk < MEOW_OVERLAY_CHK_MS) return s_last_res;
s_last_chk = now;
s_last_res = (access(s_path, F_OK) == 0) ? 1 : 0;
return s_last_res;
}
unsigned long long meow_gfx_overlay_suppressed_count(void) { return g_overlay_suppressed; }
static void present(void)
{
if (!g_init_ok || !g_back_buf) return;
if (!(g_dirty_full || g_ndirty > 0)) return;
/* §10.5 叠加让位自抑(仅 App 进程):App 前台播放中唤起通知中心时 launcher 不 SIGSTOP
* (保 PCM 供流,ncm 播放线程同进程),改由 App 进程在此自抑上屏——绘制照常进 back_buf,
* 只是不 present 到 fb,防 App 重绘穿透 launcher 叠加层。系统 UI 进程(launcher 渲染面板)
* 不抑制。抑制期脏区保留累积(超上限自然升级整帧),标记消失后下一次 present 一次上屏。 */
if (overlay_present_suppressed()) { g_overlay_suppressed++; return; }
/* 帧率门控(A-1/A-2,从 launcher 迁入):present 距上次 ≥ period(1000/eff_rate) 才呈现;
* 未满周期则保留脏区(不 dirty_reset),下一轮再试。refresh_always_max=1 时跳过门控(恒满速)。 */
unsigned long long now = now_ms();
+49
View File
@@ -136,6 +136,30 @@ static int meow_input_jack_debounce(void)
return g_jack_changed_at && (now - g_jack_changed_at) < 800;
}
/* ---- §10.5 叠加让位:通知中心叠加会话期输入排空(播放中 App 不被 SIGSTOP 的配套)----
* 媒体播放中的 App 前台唤起通知中心时 launcher 不冻结 App(保 PCM 供流,ncm 播放线程同进程),
* App 输入线程仍在读同一批 evdev 设备(每 fd 各收一份事件副本,与 launcher 互不剥夺)——若不
* 抑制,面板上的一次点击会被 App 再消费一次(如面板媒体卡片「暂停」与 App 同坐标控件双重触发)。
* 故标记存在期:排空本进程 fd 队列并丢弃、EVIOCGKEY 位图静默对齐(不产生边沿语义,系统键动作
* 叠加期由 launcher/watchdog 单侧承担)。200ms 节流缓存存在性(与 present 层同口径,别每圈 stat)。 */
static int overlay_input_suppressed(void)
{
static char s_path[108];
static int s_path_init = 0;
static unsigned long long s_last_chk = 0;
static int s_last_res = 0;
if (!s_path_init) {
const char *e = getenv("MEOW_NOTIF_ACTIVE_FILE"); /* 宿主/单测覆写(进程内只解析一次) */
snprintf(s_path, sizeof s_path, "%s", (e && e[0]) ? e : "/tmp/meow_notif_active");
s_path_init = 1;
}
unsigned long long now = now_ms();
if (s_last_chk && now - s_last_chk < 200) return s_last_res;
s_last_chk = now;
s_last_res = (access(s_path, F_OK) == 0) ? 1 : 0;
return s_last_res;
}
/* ---- 系统按键语义(M2.55:SDK 输入层消化,App 前台也生效)----
* 物理键是系统资源(APPS.md §3.4.1),语义全局统一。本函数在输入线程执行系统动作:
* 电源 116 → 息屏/亮屏/长按菜单(内核/sys_server/watchdog),SDK 不动(维持现状);
@@ -451,9 +475,34 @@ static void *input_thread(void *arg)
}
}
int overlay_was = 0; /* §10.5:上一圈是否处于叠加抑制(进入沿复位触摸状态机用) */
while (g_thread_run) {
if (nfd == 0) { usleep(20000); continue; }
int r = poll(fds, nfd, 20);
/* §10.5 叠加让位:通知中心叠加会话期(标记存在)排空并丢弃本进程事件副本——面板输入
* 由 launcher 接管,App 不得再消费(防面板点击被 App 双重触发);EVIOCGKEY 位图静默
* 对齐(不产生边沿语义/系统键动作,解除后不会把面板期按键误判为新边沿)。 */
if (overlay_input_suppressed()) {
if (!overlay_was) { /* 进入沿:复位触摸状态机,防抑制期丢失抬手致 t_down 卡死 */
overlay_was = 1;
t_down = 0; t_press_valid = 0; t_origin_valid = 0;
t_cx = t_cy = -1; t_dx = t_dy = 0; t_down_ms = 0; t_mv_x = t_mv_y = -1;
t_long_fired = 0;
}
if (r > 0)
for (int i = 0; i < nfd; i++) {
if (!(fds[i].revents & POLLIN)) continue;
unsigned char junk[64];
while (read(fds[i].fd, junk, sizeof junk) > 0) { /* 整批丢弃 */ }
}
for (int i = 0; i < nfd; i++) {
unsigned char cur[KEY_MAX / 8 + 1];
if (ioctl(fds[i].fd, EVIOCGKEY(sizeof cur), cur) == 0)
memcpy(prev_key[i], cur, sizeof cur);
}
continue;
}
overlay_was = 0;
if (r < 0) {
if (errno != EINTR) usleep(20000);
} else if (r > 0) {
+4
View File
@@ -148,6 +148,10 @@ void meow_gfx_ring_arc(int cx, int cy, int r, int th, int start_deg,
/* 是否运行于真实 fb 之上(0 = 头less/离屏,qemu/宿主)。 */
int meow_gfx_is_device(void);
/* §10.5 叠加让位:present 因 /tmp/meow_notif_active 存在而被自抑的累计次数
* (仅 App 进程会自抑;宿主单测断言用,设备上无调用方)。 */
unsigned long long meow_gfx_overlay_suppressed_count(void);
/* 中文/日文字库(mfont)是否已加载(状态栏「蓝牙」CJK 与回退 "BT" 二选一)。 */
int meow_gfx_mfont_ready(void);
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+97
View File
@@ -0,0 +1,97 @@
/* test_overlay_suppress.c — §10.5 叠加让位 present 自抑宿主验证(离屏 480x720,16bpp)。
*
* 背景:媒体播放中的 App 前台唤起通知中心时 launcher 不 SIGSTOP(保 PCM 供流),改由 SDK
* present 层见 /tmp/meow_notif_active(宿主用 MEOW_NOTIF_ACTIVE_FILE 覆写)自抑上屏。
*
* 覆盖:
* T1 无标记:flush 正常上屏(frames_presented 递增,抑制计数 0);
* T2 有标记:flush 被抑制(只画进 back_buf 不 present:frames 不变、抑制计数递增);
* T2b 抑制期第二次 flush 仍被抑制(200ms 节流缓存内行为一致);
* T3 标记消失:下一次 flush 恢复上屏(抑制期累积脏区一次上屏,无需重画);
* T4 系统 UI 进程(launcher,system_mode=1)即使有标记也不抑制(面板必须照常上屏)。
*
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
* gcc -Wall -Wextra -o test_overlay_suppress test_overlay_suppress.c test_host_stub.c \
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \
* meow_ui_draw.c meow_ui_view.c meow_notify_ipc.c \
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread
*/
#include "meow_ui.h"
#include "meow_ui_internal.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <fcntl.h>
#define MARKER "/tmp/test_notif_active_sdk"
static void marker_write(void)
{
int fd = open(MARKER, O_WRONLY | O_CREAT | O_TRUNC, 0644);
assert(fd >= 0);
if (write(fd, "1", 1) < 0) assert(0);
close(fd);
}
int main(void)
{
setenv("MEOW_UI_DUMP", "/tmp/overlay_suppress.ppm", 1);
setenv("MEOW_NOTIF_ACTIVE_FILE", MARKER, 1);
unlink(MARKER);
meow_ui_opts o = { 480, 720 };
assert(meow_ui_init(&o) == 0);
meow_ui_set_refresh_always_max(1); /* 绕过帧率门控:每次 flush 都直达 present 判定 */
/* T1:无标记正常上屏(默认 App 进程模式:render_enabled==0) */
assert(meow_ui_sys_render_enabled() == 0);
meow_ui_clear(0x112233);
meow_ui_flush();
assert(meow_ui_frames_presented() == 1);
assert(meow_gfx_overlay_suppressed_count() == 0);
printf("T1 PASS: no marker -> presented=1 suppressed=0\n");
/* T2:有标记 → 抑制(等 >200ms 越过存在性节流缓存) */
marker_write();
usleep(250000);
unsigned long long f0 = meow_ui_frames_presented();
meow_ui_clear(0x223344);
meow_ui_flush();
assert(meow_ui_frames_presented() == f0);
assert(meow_gfx_overlay_suppressed_count() == 1);
printf("T2 PASS: marker present -> suppressed (frames frozen at %llu)\n", f0);
/* T2b:抑制期再次 flush 仍抑制(节流缓存内一致) */
meow_ui_clear(0x334455);
meow_ui_flush();
assert(meow_ui_frames_presented() == f0);
assert(meow_gfx_overlay_suppressed_count() == 2);
printf("T2b PASS: still suppressed within throttle window\n");
/* T3:标记消失 → 恢复上屏(抑制期累积的整帧脏一次上屏,无需重画) */
unlink(MARKER);
usleep(250000);
meow_ui_flush();
assert(meow_ui_frames_presented() == f0 + 1);
assert(meow_gfx_overlay_suppressed_count() == 2);
printf("T3 PASS: marker gone -> present resumed (accumulated dirty flushed)\n");
/* T4:系统 UI 进程(launcher)不抑制——通知中心面板本身必须照常上屏 */
meow_ui_sys_set_system_mode(1);
assert(meow_ui_sys_render_enabled() == 1);
marker_write();
usleep(250000);
unsigned long long f1 = meow_ui_frames_presented();
meow_ui_clear(0x445566);
meow_ui_flush();
assert(meow_ui_frames_presented() == f1 + 1);
assert(meow_gfx_overlay_suppressed_count() == 2); /* 不再增长 */
unlink(MARKER);
printf("T4 PASS: system-UI process (launcher) never suppressed\n");
meow_ui_shutdown();
printf("ALL PASS\n");
return 0;
}
Binary file not shown.