通知模型补全(agent-107: SDK icon+update_progress / IPC urgency+MSG_NOTIFY_PROGRESS / 存储 urgency+icon回退+progress / CSV加3列) + 全屏收合视觉+滑条(agent-108)

This commit is contained in:
MeowOS Dev committed 2026-08-30 13:02:17 +08:00
1 parent b3e57d4b55
commit e8fa373a2f
18 files changed
+410 -56

No files matched your search

+15 -4
View File
@@ -44,6 +44,7 @@ enum {
MSG_NOTIFY_ACK = 0x0021, /* ↓ 落库结果 */
MSG_NOTIFY_DISMISS = 0x0022, /* ↑ 撤下通知 */
MSG_NOTIFY_LIST = 0x0023, /* ↓ 下发通知清单(重同步) */
MSG_NOTIFY_PROGRESS = 0x0024,/* ↑ 实时更新通知进度(仅改内存活跃槽,不落盘) */
MSG_ACT_DECLARE = 0x0030, /* ↑ (重)声明路由表 */
MSG_ACT_QUERY = 0x0031, /* ↑ 解析 activity 链路 */
MSG_ACT_RESULT = 0x0032, /* ↓ 返回链路 */
@@ -134,9 +135,11 @@ typedef struct m_act_declare {
} m_act_declare_t;
/* MSG_NOTIFY ↑:推送/更新通知(freedesktop 对齐 + MeowOS 扩展;定长 LE)。
* - summary/body/app_icon/expire_timeout 直通 freedesktop 字段;
* - summary/body/app_icon/expire_timeout 直通 freedesktop 字段;app_icon 即 SDK `icon`
* (icon name 或 path;映射为空串时 launcher 用该 app 默认图标回退);
* - target_activity/progress 为 MeowOS 专有(存储层映射为 hints 的
* meow.target_activity / meow.progress);
* - urgency 为 MeowOS 扩展(0-3,缺省 NORMAL;旧调用置 0=旧 pad 位);
* - actions[]/hints{} v1 走定长空串(M2b D-Bus 桥再填);media/ongoing 标记。
* 宿主:NOTIFICATION_SYSTEM.md §2/§4 + FRAMEWORK_DESIGN §4/附C。 */
typedef struct m_notify {
@@ -144,14 +147,22 @@ typedef struct m_notify {
char category[24]; /* 通知类型(媒体通知="media") */
char summary[64];
char body[96];
char app_icon[48];
char app_icon[48]; /* SDK icon;空=launcher 按 app_id 回退 app 图标 */
char target_activity[MEOW_ACT_MAX]; /* MeowOS:点击跳转目标 activity */
uint32_t replaces_id; /* 0=新建;>0=替换已存在通知 */
uint32_t expire_timeout; /* ms;0xFFFFFFFF=ONGOING 常驻 */
int32_t progress; /* MeowOS:0-100;<0=无 */
uint8_t media; /* 1=媒体通知 */
uint8_t ongoing; /* 1=常驻通知 */
uint16_t pad;
} m_notify_t; /* 定长 LE */
uint8_t urgency; /* 0-3(低/普通/高/紧急);越界由 SDK 钳位缺省 NORMAL;旧调用置 0=旧 pad 位 → LOW/静默 */
uint8_t pad;
} m_notify_t; /* 定长 LE;尾部 4 字节(media/ongoing/urgency/pad)与旧版 pad 等宽 */
/* MSG_NOTIFY_PROGRESS ↑:实时更新一条已存在通知的进度(仅改 launcher 内存活跃槽,
* 不落盘——TF 快照只在创建/结束(100% 或 dismiss)写)。 */
typedef struct m_notify_progress {
uint32_t nid; /* launcher 分配的通知 id(MSG_NOTIFY_ACK.status) */
int32_t progress; /* 0-100;<0=清除进度条 */
} m_notify_progress_t; /* 8 B */
#endif /* MEOW_IPC_H */
Binary file not shown.
+31 -1
View File
@@ -32,9 +32,13 @@
* app_id="system" 同走配额);TTL 10s 转储钩子(§8,TF 落盘 M2c 后置,预留 notif_dump_cb);
* 满丢该类型最旧条(ring 语义);ONGOING 常驻(转储后仍保留,服务死随清)。
* - meow_settings 默认表新增 notify.categories/category_quota/media_quota(B2a 后端)。
* - IPC:ipc_dispatch 新增 MSG_NOTIFY→MSG_NOTIFY_ACK、MSG_NOTIFY_DISMISS→MSG_ACK;
* - IPC:ipc_dispatch 新增 MSG_NOTIFY→MSG_NOTIFY_ACK、MSG_NOTIFY_DISMISS→MSG_ACK、
* MSG_NOTIFY_PROGRESS→MSG_ACK(实时进度仅改内存活跃槽,不落盘);
* MSG_NOTIFY_LIST 为守护→App 下行(M5 接入)。meow_ipc 测试钩子新增 notify/dismiss 子命令。
* - meow_notif_before_poweroff() 挂入 sys_do_reboot():正常电源事件前先转储未上卡通知再清板载。
* - 通知模型补全(2026-08-30,用户定案):SDK→IPC→存储→dump 全链路补 urgency(0-3)/
* icon(空→按 app_id 回退该 app 默认图标);progress 实时仅改内存(MSG_NOTIFY_PROGRESS),
* TF 快照只在创建/结束(100% 或 dismiss)(meow_notif_dump_tf 写当时值)。
*
* B2a 统一系统设置后端(2026-08-29):
* - 新增 meow_settings.h/.c(SYSTEM_SETTINGS.md §3/MEOWOS_API §2):全部系统设置
@@ -2460,6 +2464,17 @@ static void notif_dump_tf_placeholder(const meow_notif_t *n)
meow_notif_dump_tf(n);
}
/* ---- 通知图标回退回调(用户定案:icon 为空 → 用该 app 默认图标)----
* post 的 app_icon 为空时调用(meow_notif.c 的 notif_resolve_icon),按 app_id 在 app
* 注册表(scan_apps 静态 app.json 的 icon)里找该 app 的图标(name/path 或空)。 */
static int notif_app_icon_fallback(const char *app_id, char *out, size_t outsz)
{
if (!app_id || !app_id[0] || !out || outsz == 0) return 0;
struct meow_acct *a = meow_acct_find(app_id);
if (a && a->icon[0]) { snprintf(out, outsz, "%s", a->icon); return 1; }
return 0; /* 无注册/无图标 → 保持空(通知中心 UI 再回退) */
}
/* 启动加载(M2.11/M2.24):统一由 meow_settings 后端读 settings.conf;
* 此处仅注册监听(一次性)+ 把 backend 当前值同步到缓存全局。重载(应用期
* watchdog 可能改音量)等价于 meow_settings_init() + 手动同步。 */
@@ -2469,6 +2484,7 @@ static void settings_load(void)
meow_settings_init(); /* 幂等:注册默认 + 读 settings.conf */
meow_notif_init(); /* M2a:通知存储初始化(幂等,读 notify.* 配额) */
meow_notif_set_dump_cb(notif_dump_tf_placeholder); /* 通知#6:挂 dump 回调 = TF 转储(meow_notif_dump_tf:一行 CSV 追加 + 10MB 滚动截断) */
meow_notif_set_icon_cb(notif_app_icon_fallback); /* icon 空 → 按 app_id 回退该 app 默认图标(app.json icon) */
if (!settings_cache_registered) {
meow_settings_add_listener(settings_sync_cache_listener);
settings_cache_registered = 1;
@@ -6951,6 +6967,7 @@ static void ipc_dispatch(struct meow_ipc_msg *m)
snprintf(n.target_activity, sizeof n.target_activity, "%s", mn.target_activity);
n.expire_timeout = mn.expire_timeout;
n.progress = mn.progress;
n.urgency = mn.urgency; /* 0-3(低/普通/高/紧急);旧调用=0 → LOW(静默),新调用缺省 NORMAL */
n.ongoing = mn.ongoing;
n.media = mn.media;
uint32_t nid = 0;
@@ -6973,6 +6990,19 @@ static void ipc_dispatch(struct meow_ipc_msg *m)
logmsg("ipc: NOTIFY_DISMISS id=%u rc=%d -> ACK", id, rc);
break;
}
case MSG_NOTIFY_PROGRESS: {
m_notify_progress_t p;
memset(&p, 0, sizeof p);
memcpy(&p, m->payload, m->plen < (uint16_t)sizeof p ? m->plen : (uint16_t)sizeof p);
/* 实时进度:仅改 launcher 内存活跃槽(不落盘);progress>=100 视为结束 → 里程碑落盘 */
int rc = meow_notif_update_progress(p.nid, p.progress);
m_ack_t a;
a.code = (rc == NOTIF_OK) ? MEOW_OK : (uint32_t)rc;
a.status = 0;
ipc_send(m->fd, MSG_ACK, m->req_id, &a, sizeof a);
logmsg("ipc: NOTIFY_PROGRESS nid=%u p=%d rc=%d -> ACK", p.nid, p.progress, rc);
break;
}
case MSG_NOTIFY_LIST:
/* ↓ 守护→App 下发通知清单(重同步)。本帧不来自客户端(无请求类型),故不在此应答;
* 需用时由守护主动经 meow_notif_serialize_list() + ipc_send 下发(M5 通知中心接入)。 */
+58 -9
View File
@@ -58,6 +58,7 @@ static int g_cat_quota = MEOW_NOTIF_CAT_QUOTA_DEF;
static int g_media_quota = MEOW_NOTIF_MEDIA_QUOTA_DEF;
static meow_notif_dump_cb g_dump_cb = NULL;
static meow_notif_icon_cb g_icon_cb = NULL;
static int g_inited = 0;
/* ---- 日志(自包含,不依赖 launcher 的 logmsg;前缀 "notif:")---- */
@@ -94,14 +95,27 @@ static void n_strncpy(char *dst, const char *src, size_t len)
dst[len - 1] = 0;
}
/* app_icon 回退(用户定案):post 的 app_icon 为空时,经 g_icon_cb(app_id) 填该 app 默认图标;
* 无回调/回调未填 → 保持空(通知中心 UI 再回退)。每条可独立图标(非空即用)。 */
static void notif_resolve_icon(meow_notif_t *s)
{
if (!g_icon_cb) return;
if (s->app_icon[0]) return;
char tmp[MEOW_NOTIF_ICON_MAX];
if (g_icon_cb(s->app_id, tmp, sizeof tmp) > 0 && tmp[0])
n_strncpy(s->app_icon, tmp, sizeof s->app_icon);
}
/* ================= TF 卡转储(通知#6 / M2c,NOTIFICATION_SYSTEM.md §5/§8)=================
* 每条通知一行 CSV 追加到 <卡根>/MeowOS/notifications/notifications.log,并按日志总大小
* 滚动截断(保留最新 10MB)。系统文件统一放 <卡根>/MeowOS/ 下(TF_CARD_LAYOUT.md)。 */
* 滚动截断(保留最新 10MB)。系统文件统一放 <卡根>/MeowOS/ 下(TF_CARD_LAYOUT.md)。
* 字段见 tf_line():timestamp,id,app_id,app_icon,category,urgency,progress,title,body,
* target_activity,ongoing,media,dismissed(CSV 转义/去换行)。 */
#define MEOW_NOTIF_TF_DIR "/usr/data/mnt/sd_0/MeowOS/notifications"
#define MEOW_NOTIF_TF_LOG MEOW_NOTIF_TF_DIR "/notifications.log"
#define MEOW_NOTIF_LOG_MAX_BYTES ((size_t)10 * 1024 * 1024) /* §8:保留最新 10MB */
/* 单行 CSV 上限:app_id(32)/category(24)/summary(64)/body(96)/target_activity(48)
/* 单行 CSV 上限:app_id(32)/category(24)/summary(64)/body(96)/app_icon(48)/target_activity(48)
* 全引号转义后仍远小于此。 */
#define MEOW_NOTIF_CSV_LINE_MAX 1024
@@ -155,26 +169,31 @@ static void csv_field(char *dst, size_t dstsz, const char *src)
}
/* 生成一行 CSV:
* timestamp,id,app_id,category,urgency,title,body,target_activity,ongoing,media,dismissed
* title<-summary;urgency:meow_notif 结构无该字段(保持既有 API),列值留空 ""。
* 返回写入长度(不含 NUL)。 */
* timestamp,id,app_id,app_icon,category,urgency,progress,title,body,target_activity,ongoing,media,dismissed
* title<-summary;app_icon<-SDK icon(空→储存时已按 app_id 回退 app 图标);urgency 0-3(缺省 NORMAL);
* progress 写当前值(-1=无进度)。返回写入长度(不含 NUL)。 */
static size_t tf_line(const meow_notif_t *n, char *buf, size_t bufsz)
{
char a_app [2 + 2 * MEOW_NOTIF_APP_ID_MAX + 2];
char a_cat [2 + 2 * MEOW_NOTIF_CAT_MAX + 2];
char a_sum [2 + 2 * MEOW_NOTIF_SUMMARY_MAX + 2];
char a_body[2 + 2 * MEOW_NOTIF_BODY_MAX + 2];
char a_icon[2 + 2 * MEOW_NOTIF_ICON_MAX + 2];
char a_tgt [2 + 2 * MEOW_NOTIF_ACTIVITY_MAX + 2];
csv_field(a_app, sizeof a_app, n->app_id);
csv_field(a_cat, sizeof a_cat, n->category);
csv_field(a_sum, sizeof a_sum, n->summary);
csv_field(a_body, sizeof a_body, n->body);
csv_field(a_icon, sizeof a_icon, n->app_icon);
csv_field(a_tgt, sizeof a_tgt, n->target_activity);
int r = snprintf(buf, bufsz,
"%llu,%u,%s,%s,\"\",%s,%s,%s,%u,%u,%u\n",
"%llu,%u,%s,%s,%s,%u,%d,%s,%s,%s,%u,%u,%u\n",
(unsigned long long)n->created_ms,
(unsigned)n->id,
a_app, a_cat, a_sum, a_body, a_tgt,
a_app, a_icon, a_cat,
(unsigned)n->urgency,
(int)n->progress,
a_sum, a_body, a_tgt,
n->ongoing ? 1u : 0u,
n->media ? 1u : 0u,
n->dismissed ? 1u : 0u);
@@ -477,8 +496,10 @@ int meow_notif_post(const meow_notif_t *n, uint32_t *out_id)
n_strncpy(s->target_activity, n->target_activity, sizeof s->target_activity);
s->expire_timeout = n->expire_timeout;
s->progress = n->progress;
s->urgency = n->urgency;
s->ongoing = n->ongoing;
s->media = n->media;
notif_resolve_icon(s); /* 空 app_icon → app 图标回退 */
if (out_id) *out_id = s->id;
notif_log("post: UPDATE id=%u app=%s cat=%s", s->id, s->app_id, s->category);
return NOTIF_OK;
@@ -533,10 +554,12 @@ int meow_notif_post(const meow_notif_t *n, uint32_t *out_id)
n_strncpy(s->target_activity, n->target_activity, sizeof s->target_activity);
s->expire_timeout = n->expire_timeout;
s->progress = n->progress;
s->urgency = n->urgency;
s->ongoing = n->ongoing;
s->media = media ? 1 : 0;
s->created_ms = wall_ms();
s->dump_at_ms = mono_ms() + MEOW_NOTIF_TTL_MS; /* §8:创建 10s 转储 */
notif_resolve_icon(s); /* 空 app_icon → app 图标回退 */
s->active = 1;
if (out_id) *out_id = s->id;
notif_log("post: NEW id=%u app=%s cat=%s media=%d ongoing=%d", s->id, s->app_id,
@@ -558,6 +581,26 @@ int meow_notif_dismiss(uint32_t id)
return NOTIF_OK;
}
/* ---- 实时更新进度:仅改内存活跃槽;progress>=100 视为「结束」→ 立即归档(里程碑落盘)---- */
int meow_notif_update_progress(uint32_t id, int progress)
{
if (!g_inited) return NOTIF_ERR_NOTINIT;
int p = (progress < -1) ? -1 : (progress > 100 ? 100 : progress); /* 钳位 [-1,100] */
int idx = slot_by_id(id);
if (idx < 0) return NOTIF_ERR_NOENT;
meow_notif_t *s = &g_slots[idx];
s->progress = p; /* 实时:只改内存活跃槽,不落盘 */
if (p >= 100) { /* 结束(100%):里程碑落盘 + 释放非 ONGOING */
int was_ongoing = s->ongoing;
archive_one(s); /* 此刻快照(progress=100 写入 dump) */
if (!was_ongoing) slot_release(idx);
else notif_log("progress: id=%u reached 100%%, archived (kept for UI)", id);
} else {
notif_log("progress: id=%u p=%d (memory only)", id, p);
}
return NOTIF_OK;
}
/* ---- count ---- */
int meow_notif_count(const char *app_id)
{
@@ -641,6 +684,12 @@ void meow_notif_set_dump_cb(meow_notif_dump_cb cb)
g_dump_cb = cb; /* NULL=恢复默认(无回调,仅日志;M2c 接 TF) */
}
/* ---- 设置图标回退回调 ---- */
void meow_notif_set_icon_cb(meow_notif_icon_cb cb)
{
g_icon_cb = cb; /* NULL=禁用(空 app_icon 保持空,通知中心 UI 再回退) */
}
/* ---- MSG_NOTIFY_LIST 序列化(守护→App 下发清单;M5 通知中心/重同步用)----
* 负载: [u16 count] + count 条 定长 meow_notif 记录(records 以 app_id/category/
* summary/body/app_icon/target_activity 为主,精简 payload,适配 MEOW_IPC_CARGO)。 */
@@ -690,8 +739,8 @@ void meow_notif_dump(const char *log_prefix)
for (int i = 0; i < MEOW_NOTIF_MAX_SLOTS; i++) {
meow_notif_t *s = &g_slots[i];
if (!s->active) continue;
notif_log("%s slot%02d id=%u app=%s cat=%s sum=%s ong=%d media=%d dumped=%d",
notif_log("%s slot%02d id=%u app=%s cat=%s sum=%s ong=%d media=%d urg=%u dumped=%d",
lp, i, s->id, s->app_id, s->category, s->summary,
s->ongoing, s->media, s->dumped);
s->ongoing, s->media, s->urgency, s->dumped);
}
}
+16 -4
View File
@@ -66,14 +66,15 @@ typedef struct meow_notif {
char category[MEOW_NOTIF_CAT_MAX]; /* 通知类型(媒体通知="media") */
char summary[MEOW_NOTIF_SUMMARY_MAX]; /* 标题(单行) */
char body[MEOW_NOTIF_BODY_MAX]; /* 正文(可多行) */
char app_icon[MEOW_NOTIF_ICON_MAX]; /* 图标 icon name 或 path */
char app_icon[MEOW_NOTIF_ICON_MAX]; /* 图标 icon name 或 path(SDK icon;空→app 图标回退) */
char actions[MEOW_NOTIF_ACTIONS_MAX]; /* actions[](NUL 分隔;v1 空,M2b 填) */
char hints[MEOW_NOTIF_HINTS_MAX]; /* hints{} 原始 "k=v;..." 串(v1 空,M2b 填) */
uint32_t expire_timeout; /* 超时 ms;0xFFFFFFFF=ONGOING 常驻 */
uint64_t created_ms; /* 创建时刻(墙钟 epoch ms,供归档/显示) */
uint64_t dump_at_ms; /* 转储时刻(monotonic ms,TTL=创建+10s;ONGOING 亦到此转储) */
int32_t progress; /* MeowOS:0-100 进度;<0=无 */
int32_t progress; /* MeowOS:0-100 进度;<0=无(实时仅改内存,落盘只在创建/结束) */
char target_activity[MEOW_NOTIF_ACTIVITY_MAX]; /* MeowOS:点击跳转目标 activity */
uint8_t urgency; /* 紧急度 0-3(低/普通/高/紧急);缺省 NORMAL */
uint8_t ongoing; /* 1=常驻通知(服务死随清) */
uint8_t media; /* 1=媒体通知(走 notify.media_quota) */
uint8_t dismissed; /* 1=已撤下 */
@@ -84,6 +85,10 @@ typedef struct meow_notif {
/* 转储回调(M2c 落盘 / 本批默认仅日志):通知归档时调用。 */
typedef void (*meow_notif_dump_cb)(const meow_notif_t *n);
/* 图标回退回调:post 的 n->app_icon 为空时调用,按 app_id 填该 app 默认图标(name/path)。
* 返回 1=已填 out;0/空=无(通知中心 UI 再回退)。NULL=禁用回退(保持空 app_icon)。 */
typedef int (*meow_notif_icon_cb)(const char *app_id, char *out, size_t outsz);
/* == 生命周期 == */
int meow_notif_init(void); /* 幂等;从 meow_settings 读 notify.* 配额(B2a) */
int meow_notif_shutdown(void); /* 保存/清理(进程退出前调;本批=归档未转储项) */
@@ -119,12 +124,19 @@ void meow_notif_before_poweroff(void);
/* 覆盖转储回调(NULL=恢复默认日志;M2c 接 TF 落盘)。 */
void meow_notif_set_dump_cb(meow_notif_dump_cb cb);
/* 覆盖图标回退回调(NULL=禁用,空 app_icon 保持空;M5 通知中心 UI 再回退)。 */
void meow_notif_set_icon_cb(meow_notif_icon_cb cb);
/* 实时更新一条活跃通知的进度(0-100,<0=清除)。仅改内存活跃槽;progress>=100 视为「结束」,
* 立即归档(落盘快照)并(非 ONGOING)释放槽——对应「里程碑落盘」。返回 NOTIF_OK/NOTIF_ERR_NOENT。 */
int meow_notif_update_progress(uint32_t id, int progress);
/* == TF 卡转储(通知#6 / M2c,NOTIFICATION_SYSTEM.md §5/§8 + MEOWOS_SPEC_REF §5.3)==
* 把 dump_cb 接到 meow_notif_dump_tf 即把每条通知追加为一行 CSV 到
* <卡根>/MeowOS/notifications/notifications.log,并按日志总大小滚动截断
* (保留最新 10MB,不被电源事件清;启动时 meow_notif_init 先清板载+内存表)。
* 字段按 meow_notif 结构(title<-summary;urgency 结构无字段故列留空;均做 CSV
* 转义/去换行)。无卡/不可写仅打点降级,绝不崩。 */
* 字段按 meow_notif 结构(title<-summary;app_icon<-SDK icon 空→app 图标回退;urgency 0-3;
* progress 当前值,-1=无;均做 CSV 转义/去换行)。无卡/不可写仅打点降级,绝不崩。 */
int meow_notif_dump_tf(const meow_notif_t *n); /* 0=成功;负=失败(无卡/IO/参数) */
void meow_notif_clear_tf(void); /* 开机清板载通知(清空文件,保留目录) */
void meow_notif_set_tf_path(const char *path); /* 覆盖转储日志路径;NULL=恢复默认卡路径(宿主单测用) */
+101 -5
View File
@@ -53,9 +53,37 @@ static void make_notif(meow_notif_t *n, uint32_t id, const char *sum, const char
strcpy(n->summary, sum);
strcpy(n->body, body);
strcpy(n->target_activity, "act");
n->urgency = 0; /* 0=LOW(缺省值) */
n->progress = -1; /* 无进度 */
n->ongoing = 1;
}
/* 回读指定 summary 的活跃通知(meow_notif_list 回调;按 g_find_key 匹配) */
static meow_notif_t g_got; static int g_got_found = 0; static char g_find_key[64];
static int find_cb(const meow_notif_t *n, void *ctx)
{
(void)ctx;
if (n && strcmp(n->summary, g_find_key) == 0) { g_got = *n; g_got_found = 1; return 1; }
return 0;
}
static meow_notif_t find_notif(const char *summary)
{
g_got_found = 0; memset(&g_got, 0, sizeof g_got);
snprintf(g_find_key, sizeof g_find_key, "%s", summary);
meow_notif_list(NULL, find_cb, NULL);
return g_got_found ? g_got : (meow_notif_t){0};
}
/* 图标回退 stub:com.app → "app_icon_name";其他 app → 无 */
static int stub_icon_cb(const char *app_id, char *out, size_t outsz)
{
if (app_id && !strcmp(app_id, "com.app")) { snprintf(out, outsz, "app_icon_name"); return 1; }
return 0;
}
/* dump_cb 类型是 void,而 meow_notif_dump_tf 返回 int——包一层(同 launcher notif_dump_tf_placeholder) */
static void dump_tf_wrapper(const meow_notif_t *n){ (void)meow_notif_dump_tf(n); }
/* --- 1) CSV 转义 + 去换行 --- */
static void test_csv_escape(const char *path)
{
@@ -71,9 +99,9 @@ static void test_csv_escape(const char *path)
char *s = read_all(path, NULL);
CHECK(s != NULL, "file exists after dump");
const char *expect =
"1234567890123,42,\"com.app\",\"general\",\"\",\"Hel\"\"lo, world line2\","
"\"Body with\ttab\",\"act\",1,1,0\n"; /* ongoing=1 media=1 dismissed=0 */
CHECK(s && strcmp(s, expect) == 0, "CSV line matches expected (escape+newline-strip)");
"1234567890123,42,\"com.app\",\"\",\"general\",0,-1,\"Hel\"\"lo, world line2\","
"\"Body with\ttab\",\"act\",1,1,0\n"; /* app_icon="" urgency=0 progress=-1 ongoing=1 media=1 dismissed=0 */
CHECK(s && strcmp(s, expect) == 0, "CSV line matches expected (escape+newline-strip+new cols)");
if (s) { fprintf(stderr, " got: %s", s); fprintf(stderr, " exp: %s", expect); }
free(s);
}
@@ -134,24 +162,92 @@ static void test_init_clear(const char *path)
free(s);
}
/* --- 4) icon 回退:app_icon 空 → 按 app_id 用该 app 默认图标;非空 → 保留 --- */
static void test_icon_fallback(const char *path)
{
meow_notif_set_icon_cb(stub_icon_cb);
meow_notif_before_poweroff(); /* 清内存表,回到干净态(dump_cb=NULL 不落盘) */
meow_notif_set_tf_path(path);
meow_notif_set_tf_max(1024 * 1024);
/* 空 app_icon → 回退 app 图标 */
meow_notif_t n;
make_notif(&n, 0, "icon-test", "");
uint32_t id = 0;
CHECK(meow_notif_post(&n, &id) == NOTIF_OK, "icon-fallback post ok");
meow_notif_t got = find_notif("icon-test");
CHECK(got.id != 0 && !strcmp(got.app_icon, "app_icon_name"), "empty app_icon → app icon fallback");
/* 非空 app_icon → 独立图标(不回退) */
make_notif(&n, 0, "icon-custom", "");
n.app_icon[0] = 0; strcpy(n.app_icon, "custom/icon.png");
CHECK(meow_notif_post(&n, &id) == NOTIF_OK, "icon-custom post ok");
got = find_notif("icon-custom");
CHECK(got.id != 0 && !strcmp(got.app_icon, "custom/icon.png"), "non-empty app_icon kept (per-notif icon)");
}
/* --- 5) progress 实时(仅内存)vs 里程碑(100% 落盘 + 释放) --- */
static void test_progress_live(const char *path)
{
meow_notif_set_icon_cb(NULL);
meow_notif_set_dump_cb(NULL);
meow_notif_before_poweroff(); /* 清内存表,回到干净态 */
meow_notif_set_tf_path(path);
meow_notif_set_tf_max(1024 * 1024);
meow_notif_set_dump_cb(dump_tf_wrapper); /* 归档即写 CSV;验证落盘快照 */
meow_notif_t n;
make_notif(&n, 0, "live", "");
n.ongoing = 0; /* 非 ONGOING,便于验证 100% 释放 */
n.progress = 0;
uint32_t id = 0;
CHECK(meow_notif_post(&n, &id) == NOTIF_OK, "progress post ok");
CHECK(meow_notif_count(NULL) == 1, "one active after post");
/* 实时 <100%:仅内存,不落盘 */
CHECK(meow_notif_update_progress(id, 70) == NOTIF_OK, "update_progress(70) ok");
CHECK(meow_notif_count(NULL) == 1, "still active after live update (not dumped)");
long len = 0; char *s = read_all(path, &len);
CHECK((s == NULL || len == 0), "no CSV written on live update (<100%)");
free(s);
/* 里程碑 100%:立即归档落盘 + 释放非 ONGOING 槽 */
CHECK(meow_notif_update_progress(id, 100) == NOTIF_OK, "update_progress(100) ok");
CHECK(meow_notif_count(NULL) == 0, "slot released after 100% milestone");
s = read_all(path, &len);
CHECK(s != NULL && len > 0, "CSV written on 100% milestone");
CHECK(s && strstr(s, ",100,\"live\"") != NULL, "milestone CSV has progress=100 title=live");
if (s) fprintf(stderr, " milestone row: %s", s);
free(s);
/* 已释放 id → NOENT */
CHECK(meow_notif_update_progress(id, 50) == NOTIF_ERR_NOENT, "update on released id -> NOENT");
meow_notif_set_dump_cb(NULL); /* 复位 */
}
int main(void)
{
char tmp[] = "/tmp/meow_notif_tf_test_XXXXXX";
char *dir = mkdtemp(tmp);
if (!dir) { perror("mkdtemp"); return 2; }
char p1[512], p2[512], p3[512];
char p1[512], p2[512], p3[512], p4[512], p5[512];
snprintf(p1, sizeof p1, "%s/csv.log", dir);
snprintf(p2, sizeof p2, "%s/trim.log", dir);
snprintf(p3, sizeof p3, "%s/init.log", dir);
snprintf(p4, sizeof p4, "%s/icon.log", dir);
snprintf(p5, sizeof p5, "%s/prog.log", dir);
CHECK(p1[0] != 0, "tmpdir ready");
test_csv_escape(p1);
test_trim(p2);
test_init_clear(p3);
test_icon_fallback(p4);
test_progress_live(p5);
/* 清理:删除日志文件 + 目录(保底) */
unlink(p1); unlink(p2); unlink(p3); rmdir(dir);
unlink(p1); unlink(p2); unlink(p3); unlink(p4); unlink(p5); rmdir(dir);
fprintf(stderr, "\n%s (failures=%d)\n", g_fail ? "== TEST FAILED ==" : "== TEST PASSED ==", g_fail);
return g_fail ? 1 : 0;