283 lines
12 KiB
C
283 lines
12 KiB
C
/*
|
||
* 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;
|
||
* - MSG_NOTIFY_PROGRESS(0x0024) ↑ 负载 = m_notify_progress_t(nid+progress,实时进度仅内存)。
|
||
*
|
||
* 设计要点:
|
||
* - 只做「一次性 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;
|
||
* SDK `icon` → app_icon(空则 launcher 按 app_id 回退 app 图标);urgency 0-3 直通(越界钳位 NORMAL);
|
||
* - 优雅降级:无 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<->SDK icon;urgency 0-3;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->app_icon, sizeof mn->app_icon, "%s", n->icon ? n->icon : "");
|
||
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;
|
||
/* urgency:越界钳位到 NORMAL(缺省);0-3 直通(launcher 侧再回退缺省) */
|
||
mn->urgency = ((int)n->urgency >= 0 && (int)n->urgency < (int)MEOW_NOTIFY_URG_COUNT)
|
||
? (uint8_t)n->urgency : (uint8_t)MEOW_NOTIFY_URG_NORMAL;
|
||
}
|
||
|
||
/* ============================================================ */
|
||
/* 传输层(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);
|
||
}
|
||
|
||
void meow_notify_ipc_update_progress(uint32_t nid, int progress)
|
||
{
|
||
int fd;
|
||
if (nid == 0) return;
|
||
m_notify_progress_t p;
|
||
memset(&p, 0, sizeof p);
|
||
p.nid = nid;
|
||
p.progress = (progress < -1) ? -1 : (progress > 100 ? 100 : progress);
|
||
fd = conn_path(ipc_sock_path());
|
||
if (fd < 0) return; /* 无 socket:静默 */
|
||
set_ack_timeout(fd);
|
||
if (!send_frame(fd, MSG_NOTIFY_PROGRESS, 0x4E4F5450u /* "NOTP" */, &p, (uint32_t)sizeof p)) {
|
||
close(fd); /* 写失败:静默 */
|
||
return;
|
||
}
|
||
/* 读回应答(launcher 对 PROGRESS 回 MSG_ACK,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);
|
||
}
|