Files
MeowOS-SDK/meow_notify_ipc.c
T

244 lines
9.9 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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 <stdio.h> /* snprintf */
#include <stdlib.h> /* getenv */
#include <string.h>
#include <errno.h> /* EINTR */
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/time.h>
/* 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);
}