/* * meow_notify_ipc.c — MeowOS SDK 通知 → launcher IPC 桥(任务 #5,SDK↔IPC 关键断点)。 * * 让 meow_notify_send() 的通知真正经 AF_UNIX IPC 送到 launcher 中央存储(meow_notif_post) * 并回读 MSG_NOTIFY_ACK 拿 launcher 分配的通知 id(nid)。协议帧与负载结构复用 launcher 的 * meow_ipc.h(同源一份、杜绝两端漂移): * - 帧头 16B:magic=0x4D4F5701 / version=1 / type / req_id / len(全小端 LE,mipsel 原生); * - MSG_NOTIFY(0x0020) ↑ 负载 = m_notify_t;MSG_NOTIFY_ACK(0x0021) ↓ 负载 = m_ack_t(status=nid); * - MSG_NOTIFY_DISMISS(0x0022) ↑ 负载 = uint32 nid。 * * 设计要点: * - 只做「一次性 connect + send + read ACK」,不维护连接/订阅循环(与 meow_ipc.c 客户端同型); * - 映射层(SDK 枚举 ↔ IPC 字段)集中在本文件:分类枚举 CAT_* ↔ 字符串 * "general"/"message"/"email"/"event"/"progress"/"media";timeout_ms → expire_timeout: * >0 该毫秒、0=不自动过期、-1(ONGOING)=0xFFFFFFFF(并置 ongoing=1);媒体类型置 media=1; * - 优雅降级:无 socket(connect 失败)/写失败/ACK 超时(SO_RCVTIMEO)/非 ACK 应答 → * 返回 0(调用方 meow_ui_notify.c 保留本地 id 兜底),绝不崩; * - 线程安全:app_id 经 pthread_once 缓存;socket 路径每次调用读 env MEOW_IPC_SOCK 覆盖 * (宿主/test 用),缺省 meow_ipc.h 的 MEOW_IPC_PATH(/usr/data/meow_ipc.sock)。 * * 约定:与 meow_ui_notify.c 同风格(M2.x 中文注释);本文件为纯传输层,不调 meow_log, * 失败诊断由调用方 meow_ui_notify.c 的 notify_deliver() 统一打点。 */ #include "meow_notify_ipc.h" #include "meow_ipc.h" /* launcher 端共享协议:帧头/m_notify_t/m_ack_t/MSG_* —— 防漂移 */ #include /* snprintf */ #include /* getenv */ #include #include /* EINTR */ #include #include #include #include #include /* ACK 读超时(ms):launcher 主循环为 IPC 单写者(eventfd 唤醒,正常 <10ms 响应), * 放宽容错 500ms;超时视为降级(返回 0,不崩)。 */ #define MEOW_NOTIFY_IPC_TIMEOUT_MS 500 /* app_id(env MEOW_LOG_APP,缺省 "app";与 meow_ui_log.c 的取值口径一致)。 */ static pthread_once_t g_once = PTHREAD_ONCE_INIT; static char g_app_id[MEOW_APP_ID_MAX] = "app"; static void ipc_init(void) { const char *s = getenv("MEOW_LOG_APP"); if (s && s[0]) snprintf(g_app_id, sizeof g_app_id, "%s", s); } /* socket 路径:优先 env MEOW_IPC_SOCK(宿主/qemu sim 覆盖),缺省 MEOW_IPC_PATH。 */ static const char *ipc_sock_path(void) { const char *s = getenv("MEOW_IPC_SOCK"); return (s && s[0]) ? s : MEOW_IPC_PATH; } const char *meow_notify_ipc_app_id(void) { pthread_once(&g_once, ipc_init); return g_app_id; } /* ============================================================ */ /* 映射层: SDK meow_notify 枚举/字段 ↔ IPC m_notify_t 字段 */ /* ============================================================ */ /* SDK 分类枚举 → IPC 字符串(与 meow_ui.h §9.1 的 MEOW_NOTIFY_CAT_* 序号对齐; * 越界钳位为 "general"——同 meow_notify_category_str 的防御语义)。 */ static const char *cat_to_str(meow_notify_category cat) { static const char *t[] = { "general", "message", "email", "event", "progress", "media" }; if ((int)cat >= 0 && (int)cat < (int)MEOW_NOTIFY_CAT_COUNT) return t[cat]; return t[MEOW_NOTIFY_CAT_GENERAL]; } /* SDK timeout_ms → IPC expire_timeout(存储/显示端语义): * >0 → 该毫秒数后过期;0 → 不自动过期(NEVER);<0 → 常驻(ONGOING)= 0xFFFFFFFF。 */ static uint32_t timeout_to_expire(int timeout_ms) { if (timeout_ms < 0) return 0xFFFFFFFFu; /* ONGOING:常驻,不自动过期 */ return (uint32_t)timeout_ms; /* 0(NEVER)/ >0(毫秒) */ } /* 构建 IPC 负载 m_notify_t(app_icon/actions/hints v1 留空,M2b D-Bus 桥再填)。 * 仅在持有调用者互斥之外、发送前调用(纯 CPU 拷贝,不阻塞)。 */ static void build_m_notify(const meow_notify *n, uint32_t replaces_id, m_notify_t *mn) { memset(mn, 0, sizeof *mn); snprintf(mn->app_id, sizeof mn->app_id, "%s", meow_notify_ipc_app_id()); snprintf(mn->category, sizeof mn->category, "%s", cat_to_str(n->cat)); snprintf(mn->summary, sizeof mn->summary, "%s", n->title ? n->title : ""); snprintf(mn->body, sizeof mn->body, "%s", n->body ? n->body : ""); snprintf(mn->target_activity, sizeof mn->target_activity, "%s", n->target_activity ? n->target_activity : ""); mn->replaces_id = replaces_id; /* 0=新建;>0=更新(不存在则落为新建,见 launcher) */ mn->expire_timeout = timeout_to_expire(n->timeout_ms); mn->progress = (n->progress < -1) ? -1 : (n->progress > 100 ? 100 : n->progress); mn->media = (n->cat == MEOW_NOTIFY_CAT_MEDIA) ? 1 : 0; mn->ongoing = (n->timeout_ms == MEOW_NOTIFY_TIMEOUT_ONGOING) ? 1 : 0; } /* ============================================================ */ /* 传输层(AF_UNIX SOCK_STREAM;一次性 connect + send + read) */ /* ============================================================ */ /* 连接 socket。失败返回 -1(不崩;调用方降级)。 */ static int conn_path(const char *path) { int fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) return -1; struct sockaddr_un a; memset(&a, 0, sizeof a); a.sun_family = AF_UNIX; strncpy(a.sun_path, path, sizeof a.sun_path - 1); if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { close(fd); return -1; } return fd; } /* 写满 n 字节(EINTR 重试);任一次写失败返回 0。 */ static int write_all(int fd, const uint8_t *p, size_t n) { size_t off = 0; while (off < n) { ssize_t w = write(fd, p + off, n - off); if (w < 0) { if (errno == EINTR) continue; return 0; } if (w == 0) return 0; off += (size_t)w; } return 1; } /* 读满 n 字节(超时/断开返回 0)。 */ static int read_all(int fd, uint8_t *p, size_t n) { size_t off = 0; while (off < n) { ssize_t r = read(fd, p + off, n - off); if (r < 0) { if (errno == EINTR) continue; return 0; } if (r == 0) return 0; off += (size_t)r; } return 1; } /* 组装并写出 16B(LE)帧头 + 负载。 */ static int send_frame(int fd, uint16_t type, uint32_t req_id, const void *payload, uint32_t len) { uint8_t hdr[MEOW_IPC_HDR_LEN]; uint32_t magic = MEOW_IPC_MAGIC; uint16_t ver = MEOW_IPC_VERSION; memcpy(hdr + 0, &magic, 4); memcpy(hdr + 4, &ver, 2); memcpy(hdr + 6, &type, 2); memcpy(hdr + 8, &req_id, 4); memcpy(hdr + 12, &len, 4); if (!write_all(fd, hdr, sizeof hdr)) return 0; if (len && payload && !write_all(fd, payload, len)) return 0; return 1; } /* 给 conn 设 ACK 读超时(失败忽略——超时降级由 read_all 兜底)。 */ static void set_ack_timeout(int fd) { struct timeval tv; tv.tv_sec = MEOW_NOTIFY_IPC_TIMEOUT_MS / 1000; tv.tv_usec = (MEOW_NOTIFY_IPC_TIMEOUT_MS % 1000) * 1000; setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); } uint32_t meow_notify_ipc_send(const meow_notify *n, uint32_t replaces_id) { m_notify_t mn; int fd; if (!n) return 0; build_m_notify(n, replaces_id, &mn); fd = conn_path(ipc_sock_path()); if (fd < 0) return 0; /* 无 socket:优雅降级 */ set_ack_timeout(fd); if (!send_frame(fd, MSG_NOTIFY, 0x4E4F5449u /* "NOTI" */, &mn, (uint32_t)sizeof mn)) { close(fd); /* 写失败:降级 */ return 0; } /* 读回 16B 应答头 + m_ack_t,取 launcher 分配的通知 id(a.status)。 */ uint8_t rhdr[MEOW_IPC_HDR_LEN]; uint32_t nid = 0; if (read_all(fd, rhdr, sizeof rhdr)) { uint32_t rmagic, rlen; uint16_t rver, rtype; memcpy(&rmagic, rhdr + 0, 4); memcpy(&rver, rhdr + 4, 2); memcpy(&rtype, rhdr + 6, 2); memcpy(&rlen, rhdr + 12, 4); if (rmagic == MEOW_IPC_MAGIC && rver == MEOW_IPC_VERSION && rtype == MSG_NOTIFY_ACK && rlen >= sizeof(m_ack_t)) { uint8_t pay[sizeof(m_ack_t)]; if (read_all(fd, pay, sizeof(m_ack_t))) { m_ack_t a; memcpy(&a, pay, sizeof a); if (a.code == MEOW_OK) nid = a.status; /* 落库成功 → nid */ } } } close(fd); return nid; /* 非 ACK/超时 → 0(降级) */ } void meow_notify_ipc_dismiss(uint32_t nid) { int fd; if (nid == 0) return; fd = conn_path(ipc_sock_path()); if (fd < 0) return; /* 无 socket:静默 */ set_ack_timeout(fd); if (!send_frame(fd, MSG_NOTIFY_DISMISS, 0x4E4F5444u /* "NOTD" */, &nid, (uint32_t)sizeof nid)) { close(fd); /* 写失败:静默 */ return; } /* 读回应答(launcher 对 DISMISS 回 MSG_ACK,见 meow_ipc.c cmd_dismiss)。虽为 * best-effort,但读回确保 launcher 已处理完撤下再关连接(避免写后即关的丢帧竞态)。 */ uint8_t rhdr[MEOW_IPC_HDR_LEN]; if (read_all(fd, rhdr, sizeof rhdr)) { uint32_t rmagic, rlen; uint16_t rver, rtype; memcpy(&rmagic, rhdr + 0, 4); memcpy(&rver, rhdr + 4, 2); memcpy(&rtype, rhdr + 6, 2); memcpy(&rlen, rhdr + 12, 4); if (rmagic == MEOW_IPC_MAGIC && rver == MEOW_IPC_VERSION && rtype == MSG_ACK && rlen >= sizeof(m_ack_t)) { uint8_t pay[sizeof(m_ack_t)]; (void)read_all(fd, pay, sizeof(m_ack_t)); /* 读到即确认;内容不关心 */ } } close(fd); }