• 全新单提交:清除含敏感信息的旧历史(sudo 口令、内网 IP、SSH 密钥路径、热点 SSID、设备 MAC) • 本地开发文档/测试记录/构建脚本全部移入 gitignored 的 devdocs/ 与 scripts/build/(不入库) • 保留:MeowOS 源码(launcher/watchdog/bootmenu/meow_svcd/SDK)、项目规范文档、mips-repo 增补组件:SDK 统一日志+通知 API、meow_svcd 服务管理器、mips-repo 软件仓库
503 lines
19 KiB
C
503 lines
19 KiB
C
/* meow_notif.c — MeowOS 通知存储 + 分发(阶段 D · M2a)
|
||
*
|
||
* 实现与设计:NOTIFICATION_SYSTEM.md §1-§8 + FRAMEWORK_DESIGN §4/附C。
|
||
* - 通知数据结构:对齐 freedesktop.org Desktop Notifications 字段
|
||
* (app_name/app_icon/summary/body/actions[]/hints{}/expire_timeout)
|
||
* + MeowOS 扩展(target_activity / progress,来自 hints)。
|
||
* - 内存存储:固定活跃槽表(ring 语义——每 App×category 配额,满丢该类型最旧条)。
|
||
* - 配额(§7,用户拍板):每 App ≤ notify.categories 个类型;每类型活跃
|
||
* 通知 ≤ notify.category_quota;媒体通知每 App ≤ notify.media_quota(允许多媒体 App
|
||
* 同时)。系统事件=App(app_id="system"),同走配额。配额只管内存活跃态。
|
||
* - TTL(§8):通知创建 10s 后自动转储到 TF(无论是否过期/被清)——本批只做
|
||
* dump 钩子/日志/回调(notif_dump_cb),实际 TF 落盘 M2c 后置。ONGOING 通知
|
||
* 转储后仍保留内存(服务死随清)。
|
||
* - 转储钩子:meow_notif_dump_now()(关机前转储所有未上卡);
|
||
* meow_notif_before_poweroff()(dump + 清板载)。
|
||
*
|
||
* 空洞实现风格:本文件自包含(不依赖 meow_launcherd.c 的 logmsg/now_ms),配额经
|
||
* meow_settings(B2a)读 notify.categories/category_quota/media_quota(键缺省用默认值),
|
||
* 文件 I/O 失败仅打点,绝不崩。注释风格中文 +「M2.a 说明」。
|
||
*/
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <strings.h> /* strcasecmp */
|
||
#include <stdarg.h>
|
||
#include <time.h>
|
||
|
||
#include "meow_notif.h"
|
||
#include "meow_settings.h" /* B2a:读 notify.* 配额(MEOW_OK/MEOW_ERR_*) */
|
||
|
||
/* == 应用/槽位容量(确定静态内存;配额常量由 meow_settings 或默认覆盖)== */
|
||
#define MEOW_NOTIF_MAX_APPS 8 /* 同时跟踪的应用数(含 "system") */
|
||
#define MEOW_NOTIF_MAX_CATS 4 /* 每 App 类型上限(notify.categories 上限) */
|
||
#define MEOW_NOTIF_MAX_SLOTS (MEOW_NOTIF_MAX_APPS * MEOW_NOTIF_MAX_CATS * MEOW_NOTIF_CAT_QUOTA_DEF) /* 128 */
|
||
|
||
/* 应用条目:已声明类型集 + 活跃媒体通知计数 */
|
||
typedef struct notif_app {
|
||
char app_id[MEOW_NOTIF_APP_ID_MAX];
|
||
char cats[MEOW_NOTIF_MAX_CATS][MEOW_NOTIF_CAT_MAX];
|
||
int ncats;
|
||
int media_count; /* 当前活跃媒体通知数 */
|
||
} notif_app_t;
|
||
|
||
static notif_app_t g_apps[MEOW_NOTIF_MAX_APPS];
|
||
static int g_napps = 0;
|
||
static meow_notif_t g_slots[MEOW_NOTIF_MAX_SLOTS];
|
||
static uint32_t g_next_id = 1;
|
||
static int g_dumped_total = 0; /* 会话内累计转储计数(统计用) */
|
||
|
||
/* 配额(运行时从 meow_settings notify.* 读;键缺省用默认) */
|
||
static int g_max_cats = MEOW_NOTIF_CATS_DEF;
|
||
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 int g_inited = 0;
|
||
|
||
/* ---- 日志(自包含,不依赖 launcher 的 logmsg;前缀 "notif:")---- */
|
||
static void notif_log(const char *fmt, ...)
|
||
{
|
||
char msg[256];
|
||
va_list ap;
|
||
va_start(ap, fmt);
|
||
vsnprintf(msg, sizeof msg, fmt, ap);
|
||
va_end(ap);
|
||
fprintf(stderr, "notif: %s\n", msg);
|
||
}
|
||
|
||
/* ---- 时钟:墙钟 epoch ms(created_ms,供归档/显示)+ monotonic ms(TTL 判定)---- */
|
||
static uint64_t wall_ms(void)
|
||
{
|
||
struct timespec ts;
|
||
clock_gettime(CLOCK_REALTIME, &ts);
|
||
return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
|
||
}
|
||
static uint64_t mono_ms(void)
|
||
{
|
||
struct timespec ts;
|
||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||
return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
|
||
}
|
||
|
||
/* ---- 短串拷贝(保证 NUL 终止 + 截断到 len-1)---- */
|
||
static void n_strncpy(char *dst, const char *src, size_t len)
|
||
{
|
||
if (!dst || len == 0) return;
|
||
if (!src) { dst[0] = 0; return; }
|
||
strncpy(dst, src, len - 1);
|
||
dst[len - 1] = 0;
|
||
}
|
||
|
||
/* ---- 配额读取(B2a;键缺省/取失败用默认值)---- */
|
||
static void read_quota(void)
|
||
{
|
||
int v;
|
||
if (meow_settings_get_int("notify.categories", &v) == MEOW_OK && v > 0)
|
||
g_max_cats = v;
|
||
if (meow_settings_get_int("notify.category_quota", &v) == MEOW_OK && v > 0)
|
||
g_cat_quota = v;
|
||
if (meow_settings_get_int("notify.media_quota", &v) == MEOW_OK && v > 0)
|
||
g_media_quota = v;
|
||
notif_log("quota: cats=%d cat_quota=%d media_quota=%d", g_max_cats, g_cat_quota, g_media_quota);
|
||
}
|
||
|
||
/* ---- 生命周期 ---- */
|
||
int meow_notif_init(void)
|
||
{
|
||
if (g_inited) return NOTIF_OK;
|
||
memset(g_apps, 0, sizeof g_apps);
|
||
memset(g_slots, 0, sizeof g_slots);
|
||
g_napps = 0;
|
||
g_next_id = 1;
|
||
g_dumped_total = 0;
|
||
read_quota(); /* B2a:notify.* 配额 */
|
||
g_inited = 1;
|
||
notif_log("init: slots=%d default dump_cb=log-only (M2c -> TF)", MEOW_NOTIF_MAX_SLOTS);
|
||
return NOTIF_OK;
|
||
}
|
||
|
||
int meow_notif_shutdown(void)
|
||
{
|
||
if (!g_inited) return NOTIF_OK;
|
||
/* 退出前归档所有未转储项(不清理;before_poweroff 负责清板载) */
|
||
meow_notif_dump_now();
|
||
return NOTIF_OK;
|
||
}
|
||
|
||
/* ---- App 表查找/创建 ---- */
|
||
static notif_app_t *find_app(const char *app_id)
|
||
{
|
||
for (int i = 0; i < g_napps; i++)
|
||
if (!strcmp(g_apps[i].app_id, app_id)) return &g_apps[i];
|
||
return NULL;
|
||
}
|
||
|
||
static notif_app_t *get_or_add_app(const char *app_id)
|
||
{
|
||
notif_app_t *a = find_app(app_id);
|
||
if (a) return a;
|
||
if (g_napps >= MEOW_NOTIF_MAX_APPS) return NULL; /* 满:丢弃(防御) */
|
||
a = &g_apps[g_napps++];
|
||
memset(a, 0, sizeof *a);
|
||
n_strncpy(a->app_id, app_id, sizeof a->app_id);
|
||
return a;
|
||
}
|
||
|
||
/* 某 App 已声明的类型是否包含 cat;无=返回 -1 */
|
||
static int app_cat_index(const notif_app_t *a, const char *cat)
|
||
{
|
||
for (int i = 0; i < a->ncats; i++)
|
||
if (!strcmp(a->cats[i], cat)) return i;
|
||
return -1;
|
||
}
|
||
|
||
/* 声明类型:已在集合→ok;否则若未超上限则加入;超上限→返回 NOTIF_ERR_QUOTA。 */
|
||
static int declare_cat(notif_app_t *a, const char *cat)
|
||
{
|
||
if (app_cat_index(a, cat) >= 0) return NOTIF_OK;
|
||
if (a->ncats >= MEOW_NOTIF_MAX_CATS || a->ncats >= g_max_cats)
|
||
return NOTIF_ERR_QUOTA; /* App 类型数超限 */
|
||
n_strncpy(a->cats[a->ncats++], cat, sizeof a->cats[0]);
|
||
return NOTIF_OK;
|
||
}
|
||
|
||
/* ---- 媒体通知判定:category 命中 MEOW_NOTIF_MEDIA_CAT(前缀)---- */
|
||
static int is_media_cat(const char *cat)
|
||
{
|
||
return (cat && strncasecmp(cat, MEOW_NOTIF_MEDIA_CAT, strlen(MEOW_NOTIF_MEDIA_CAT)) == 0);
|
||
}
|
||
|
||
/* ---- 槽位 ---- */
|
||
static int slot_free_index(void)
|
||
{
|
||
for (int i = 0; i < MEOW_NOTIF_MAX_SLOTS; i++)
|
||
if (!g_slots[i].active) return i;
|
||
return -1;
|
||
}
|
||
|
||
/* 统计某 App × 某类型 的活跃(active 且未撤下)通知数 —— 配额「活跃(未过期)」含义:
|
||
* 瞬时通知 10s 后已释放(不占位);ONGOING 常驻(转储后仍占位)——故只看 dismissed。 */
|
||
static int count_active_cat(const notif_app_t *a, const char *cat)
|
||
{
|
||
int n = 0;
|
||
for (int i = 0; i < MEOW_NOTIF_MAX_SLOTS; i++) {
|
||
meow_notif_t *s = &g_slots[i];
|
||
if (s->active && !s->dismissed &&
|
||
!strcmp(s->app_id, a->app_id) && !strcmp(s->category, cat))
|
||
n++;
|
||
}
|
||
return n;
|
||
}
|
||
|
||
/* 统计某 App 的活跃媒体通知数(同样只看 dismissed;ONGOING 媒体仍占配额) */
|
||
static int count_active_media(const notif_app_t *a)
|
||
{
|
||
int n = 0;
|
||
for (int i = 0; i < MEOW_NOTIF_MAX_SLOTS; i++) {
|
||
meow_notif_t *s = &g_slots[i];
|
||
if (s->active && !s->dismissed && s->media &&
|
||
!strcmp(s->app_id, a->app_id)) n++;
|
||
}
|
||
return n;
|
||
}
|
||
|
||
/* 找某 App × 某类型 最旧(created_ms 最小)活跃通知槽;无=返回 -1 */
|
||
static int oldest_slot_cat(const notif_app_t *a, const char *cat)
|
||
{
|
||
int best = -1;
|
||
uint64_t best_ms = UINT64_MAX;
|
||
for (int i = 0; i < MEOW_NOTIF_MAX_SLOTS; i++) {
|
||
meow_notif_t *s = &g_slots[i];
|
||
if (s->active && !s->dismissed &&
|
||
!strcmp(s->app_id, a->app_id) && !strcmp(s->category, cat)) {
|
||
if (s->created_ms < best_ms) { best_ms = s->created_ms; best = i; }
|
||
}
|
||
}
|
||
return best;
|
||
}
|
||
|
||
/* 找某 App 最旧媒体通知槽;无=返回 -1 */
|
||
static int oldest_media_slot(const notif_app_t *a)
|
||
{
|
||
int best = -1;
|
||
uint64_t best_ms = UINT64_MAX;
|
||
for (int i = 0; i < MEOW_NOTIF_MAX_SLOTS; i++) {
|
||
meow_notif_t *s = &g_slots[i];
|
||
if (s->active && !s->dismissed && s->media &&
|
||
!strcmp(s->app_id, a->app_id)) {
|
||
if (s->created_ms < best_ms) { best_ms = s->created_ms; best = i; }
|
||
}
|
||
}
|
||
return best;
|
||
}
|
||
|
||
/* 归档(dump)单条:调 dump_cb + 标 dumped。返回是否首次转储。 */
|
||
static int archive_one(meow_notif_t *s)
|
||
{
|
||
if (!s->active || s->dumped) return 0;
|
||
if (g_dump_cb) g_dump_cb(s); /* M2c 替换为 TF 落盘;本批默认日志/空 */
|
||
s->dumped = 1;
|
||
g_dumped_total++;
|
||
notif_log("dump: id=%u app=%s cat=%s ongoing=%d dumped_total=%d",
|
||
s->id, s->app_id, s->category, s->ongoing, g_dumped_total);
|
||
return 1;
|
||
}
|
||
|
||
/* 释放槽(清 active,槽位可复用) */
|
||
static void slot_release(int idx)
|
||
{
|
||
if (idx < 0 || idx >= MEOW_NOTIF_MAX_SLOTS) return;
|
||
memset(&g_slots[idx], 0, sizeof g_slots[idx]);
|
||
}
|
||
|
||
/* 按 id 找活跃槽;无=返回 -1 */
|
||
static int slot_by_id(uint32_t id)
|
||
{
|
||
for (int i = 0; i < MEOW_NOTIF_MAX_SLOTS; i++)
|
||
if (g_slots[i].active && g_slots[i].id == id) return i;
|
||
return -1;
|
||
}
|
||
|
||
/* ---- 核心:post ---- */
|
||
int meow_notif_post(const meow_notif_t *n, uint32_t *out_id)
|
||
{
|
||
int rc = NOTIF_OK;
|
||
if (out_id) *out_id = 0;
|
||
if (!g_inited) return NOTIF_ERR_NOTINIT;
|
||
if (!n || !n->app_id[0]) return NOTIF_ERR_INVAL;
|
||
if (!n->category[0]) return NOTIF_ERR_INVAL;
|
||
|
||
/* 更新(replace)语义:id>0 且已存在 → 就地更新字段 */
|
||
if (n->id != 0) {
|
||
int idx = slot_by_id(n->id);
|
||
if (idx >= 0) {
|
||
meow_notif_t *s = &g_slots[idx];
|
||
n_strncpy(s->category, n->category, sizeof s->category);
|
||
n_strncpy(s->summary, n->summary, sizeof s->summary);
|
||
n_strncpy(s->body, n->body, sizeof s->body);
|
||
n_strncpy(s->app_icon, n->app_icon, sizeof s->app_icon);
|
||
n_strncpy(s->actions, n->actions, sizeof s->actions);
|
||
n_strncpy(s->hints, n->hints, sizeof s->hints);
|
||
n_strncpy(s->target_activity, n->target_activity, sizeof s->target_activity);
|
||
s->expire_timeout = n->expire_timeout;
|
||
s->progress = n->progress;
|
||
s->ongoing = n->ongoing;
|
||
s->media = n->media;
|
||
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;
|
||
}
|
||
/* 指定 id 但不存在 → 落为新建(freedesktop 行为:replaces_id 不存在则新建) */
|
||
}
|
||
|
||
notif_app_t *a = get_or_add_app(n->app_id);
|
||
if (!a) { notif_log("post: app table full, drop app=%s", n->app_id); return NOTIF_ERR_NOMEM; }
|
||
|
||
rc = declare_cat(a, n->category);
|
||
if (rc != NOTIF_OK) {
|
||
notif_log("post: QUOTA app=%s cat=%s (cats=%d max=%d)", n->app_id, n->category,
|
||
a->ncats, g_max_cats);
|
||
return rc;
|
||
}
|
||
|
||
int media = (n->media != 0) || is_media_cat(n->category);
|
||
/* 配额 + 满丢该类型最旧条 */
|
||
if (media) {
|
||
if (count_active_media(a) >= g_media_quota) {
|
||
int old = oldest_media_slot(a);
|
||
if (old >= 0) {
|
||
notif_log("post: media quota full app=%s -> drop id=%u", n->app_id, g_slots[old].id);
|
||
slot_release(old); /* 满丢最旧媒体通知 */
|
||
}
|
||
}
|
||
} else {
|
||
if (count_active_cat(a, n->category) >= g_cat_quota) {
|
||
int old = oldest_slot_cat(a, n->category);
|
||
if (old >= 0) {
|
||
notif_log("post: cat quota full app=%s cat=%s -> drop id=%u", n->app_id,
|
||
n->category, g_slots[old].id);
|
||
slot_release(old); /* 满丢该类型最旧条 */
|
||
}
|
||
}
|
||
}
|
||
|
||
int idx = slot_free_index();
|
||
if (idx < 0) { notif_log("post: NOMEM slots full app=%s", n->app_id); return NOTIF_ERR_NOMEM; }
|
||
|
||
meow_notif_t *s = &g_slots[idx];
|
||
memset(s, 0, sizeof *s);
|
||
s->id = g_next_id++;
|
||
n_strncpy(s->app_id, n->app_id, sizeof s->app_id);
|
||
n_strncpy(s->category, n->category, sizeof s->category);
|
||
n_strncpy(s->summary, n->summary, sizeof s->summary);
|
||
n_strncpy(s->body, n->body, sizeof s->body);
|
||
n_strncpy(s->app_icon, n->app_icon, sizeof s->app_icon);
|
||
n_strncpy(s->actions, n->actions, sizeof s->actions);
|
||
n_strncpy(s->hints, n->hints, sizeof s->hints);
|
||
n_strncpy(s->target_activity, n->target_activity, sizeof s->target_activity);
|
||
s->expire_timeout = n->expire_timeout;
|
||
s->progress = n->progress;
|
||
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 转储 */
|
||
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,
|
||
s->category, s->media, s->ongoing);
|
||
return NOTIF_OK;
|
||
}
|
||
|
||
/* ---- dismiss:撤下 + 立即归档 + 释放槽 ---- */
|
||
int meow_notif_dismiss(uint32_t id)
|
||
{
|
||
if (!g_inited) return NOTIF_ERR_NOTINIT;
|
||
int idx = slot_by_id(id);
|
||
if (idx < 0) return NOTIF_ERR_NOENT;
|
||
meow_notif_t *s = &g_slots[idx];
|
||
archive_one(s); /* 未被清/过期也已转储(§8) */
|
||
s->dismissed = 1;
|
||
notif_log("dismiss: id=%u app=%s", id, s->app_id);
|
||
slot_release(idx);
|
||
return NOTIF_OK;
|
||
}
|
||
|
||
/* ---- count ---- */
|
||
int meow_notif_count(const char *app_id)
|
||
{
|
||
if (!g_inited) return -NOTIF_ERR_NOTINIT;
|
||
int n = 0;
|
||
for (int i = 0; i < MEOW_NOTIF_MAX_SLOTS; i++) {
|
||
meow_notif_t *s = &g_slots[i];
|
||
if (s->active && !s->dismissed &&
|
||
(!app_id || !app_id[0] || !strcmp(s->app_id, app_id)))
|
||
n++;
|
||
}
|
||
return n;
|
||
}
|
||
|
||
/* ---- list ---- */
|
||
int meow_notif_list(const char *app_id,
|
||
int (*cb)(const meow_notif_t *n, void *ctx), void *ctx)
|
||
{
|
||
if (!g_inited) return -NOTIF_ERR_NOTINIT;
|
||
if (!cb) return -NOTIF_ERR_INVAL;
|
||
int n = 0;
|
||
for (int i = 0; i < MEOW_NOTIF_MAX_SLOTS; i++) {
|
||
meow_notif_t *s = &g_slots[i];
|
||
if (!s->active || s->dismissed) continue;
|
||
if (app_id && app_id[0] && strcmp(s->app_id, app_id)) continue;
|
||
n++;
|
||
if (cb(s, ctx) != 0) break;
|
||
}
|
||
return n;
|
||
}
|
||
|
||
/* ---- dump_now:把当前状态「排空到 TTL 归档态」——归档所有未转储;非 ONGOING 释放槽,
|
||
* ONGOING 转储后保留(常驻)。等价「立即触发所有 slot 的 §8 转储」。停机前用。 ---- */
|
||
int meow_notif_dump_now(void)
|
||
{
|
||
if (!g_inited) return -NOTIF_ERR_NOTINIT;
|
||
int n = 0;
|
||
for (int i = 0; i < MEOW_NOTIF_MAX_SLOTS; i++) {
|
||
meow_notif_t *s = &g_slots[i];
|
||
if (!s->active || s->dumped) continue;
|
||
int was_ongoing = s->ongoing;
|
||
n += archive_one(s);
|
||
if (!was_ongoing) slot_release(i); /* 与 tick 一致:非 ONGOING 转储后释放槽 */
|
||
else notif_log("dump_now: ongoing id=%u archived (kept for UI)", s->id);
|
||
}
|
||
return n;
|
||
}
|
||
|
||
/* ---- tick:TTL 转储 + 释放非 ONGOING;ONGOING 转储后保留 ---- */
|
||
void meow_notif_tick(void)
|
||
{
|
||
if (!g_inited) return;
|
||
uint64_t now = mono_ms();
|
||
for (int i = 0; i < MEOW_NOTIF_MAX_SLOTS; i++) {
|
||
meow_notif_t *s = &g_slots[i];
|
||
if (!s->active || s->dumped) continue;
|
||
if (now >= s->dump_at_ms) { /* §8:10s 到点自动转储 */
|
||
int was_ongoing = s->ongoing;
|
||
archive_one(s);
|
||
if (!was_ongoing) slot_release(i); /* 非 ONGOING:转储后释放槽 */
|
||
else notif_log("tick: ongoing id=%u archived (kept for UI)", s->id);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* ---- before_poweroff:转储所有未上卡 + 清板载 ---- */
|
||
void meow_notif_before_poweroff(void)
|
||
{
|
||
if (!g_inited) return;
|
||
int n = meow_notif_dump_now();
|
||
notif_log("before_poweroff: dumped=%d, clearing board", n);
|
||
memset(g_slots, 0, sizeof g_slots);
|
||
memset(g_apps, 0, sizeof g_apps);
|
||
g_napps = 0;
|
||
g_next_id = 1;
|
||
}
|
||
|
||
/* ---- 设置 dump 回调 ---- */
|
||
void meow_notif_set_dump_cb(meow_notif_dump_cb cb)
|
||
{
|
||
g_dump_cb = cb; /* NULL=恢复默认(无回调,仅日志;M2c 接 TF) */
|
||
}
|
||
|
||
/* ---- MSG_NOTIFY_LIST 序列化(守护→App 下发清单;M5 通知中心/重同步用)----
|
||
* 负载: [u16 count] + count 条 定长 meow_notif 记录(records 以 app_id/category/
|
||
* summary/body/app_icon/target_activity 为主,精简 payload,适配 MEOW_IPC_CARGO)。 */
|
||
int meow_notif_serialize_list(const char *app_id, uint8_t *buf, int buf_len)
|
||
{
|
||
if (!g_inited || !buf || buf_len < (int)(2 + sizeof(uint16_t))) return -NOTIF_ERR_INVAL;
|
||
/* 简化 v1:序列化 count(u16)+ 每条固定 128B 之摘要记录(id/category/summary/body/
|
||
* app_icon/target_activity/ongoing/progress/media/dismissed)。M5 全套结构后调。 */
|
||
uint16_t count = 0;
|
||
uint8_t *p = buf + 2;
|
||
int cap = buf_len - 2;
|
||
const uint16_t rec = 128;
|
||
for (int i = 0; i < MEOW_NOTIF_MAX_SLOTS; i++) {
|
||
meow_notif_t *s = &g_slots[i];
|
||
if (!s->active || s->dismissed) continue;
|
||
if (app_id && app_id[0] && strcmp(s->app_id, app_id)) continue;
|
||
if ((int)(rec) > cap) break;
|
||
memset(p, 0, rec);
|
||
uint32_t id = s->id;
|
||
memcpy(p, &id, 4); /* +0 id */
|
||
n_strncpy((char *)(p + 4), s->category, 24); /* +4 category[24] */
|
||
n_strncpy((char *)(p + 28), s->summary, 64); /* +28 summary[64] */
|
||
n_strncpy((char *)(p + 92), s->body, 24); /* +92 body[24](v1 截断) */
|
||
p[116] = s->ongoing;
|
||
p[117] = s->media;
|
||
p[118] = s->dismissed;
|
||
/* +119 target_activity[8] 预留;v1 截断,M5 扩充 */
|
||
memcpy(p + 119, s->target_activity, 8);
|
||
p += rec;
|
||
cap -= rec;
|
||
count++;
|
||
}
|
||
memcpy(buf, &count, 2);
|
||
return 2 + (int)count * rec;
|
||
}
|
||
|
||
/* ---- 调试打印 ---- */
|
||
void meow_notif_dump(const char *log_prefix)
|
||
{
|
||
const char *lp = log_prefix ? log_prefix : "";
|
||
notif_log("%sactive=%d apps=%d next_id=%u dumped_total=%d",
|
||
lp, meow_notif_count(NULL), g_napps, g_next_id, g_dumped_total);
|
||
for (int i = 0; i < g_napps; i++) {
|
||
notif_log("%s app=%s cats=%d media=%d", lp, g_apps[i].app_id,
|
||
g_apps[i].ncats, g_apps[i].media_count);
|
||
}
|
||
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",
|
||
lp, i, s->id, s->app_id, s->category, s->summary,
|
||
s->ongoing, s->media, s->dumped);
|
||
}
|
||
}
|