WIP checkpoint (M2.53→M2.54 工作区): 字体归MeowOS/fonts+SDK跟随+多字体(进行中)+boot板载+状态栏28+亮度/音量修复+文档刷新
This commit is contained in:
1 parent
245bee2946
commit
064635b3c4
44 files changed
+2834
-308
No files matched your search
@@ -9,10 +9,12 @@ AR="${AR:-/usr/bin/ar}" # 系统 GNU ar 即可归档 mipsel ELF 对象(架
|
||||
|
||||
rm -f meow_ui.a *.o
|
||||
|
||||
OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_ui_sys stb_image"
|
||||
# -I../meow_launcherd:meow_notify_ipc.c 复用共享协议头 meow_ipc.h(同一份、杜绝两端漂移)
|
||||
OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_ui_sys meow_ui_vol stb_image"
|
||||
CFLAGS="-Os -static -I$ROOT/patch_v2/meow_launcherd"
|
||||
for f in $OBJS; do
|
||||
echo "== cc $f =="
|
||||
"$CC" -Os -static -c "$f.c" -o "$f.o"
|
||||
"$CC" $CFLAGS -c "$f.c" -o "$f.o"
|
||||
done
|
||||
|
||||
# meow_font.o(复用 launcher 字库,打进 meow_ui.a → 应用只链一个库)
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* meow_notify_ipc.h — MeowOS SDK 通知 → launcher IPC 桥(内部头,不随 meow_ui.h 发布给应用)。
|
||||
*
|
||||
* 定位(任务 #5 SDK↔IPC 桥关键断点):让框架 app 调 meow_notify_send() 的通知真正经
|
||||
* AF_UNIX IPC 送到 launcher(meow_launcherd)中央存储(meow_notif.c 的 meow_notif_post),
|
||||
* 通知中心(阶段 B · M5)才能显示。消息通道(FRAMEWORK_DESIGN §1 / meow_ipc.h):
|
||||
* - MSG_NOTIFY 0x0020 ↑:推送/更新通知(负载 m_notify_t);
|
||||
* - MSG_NOTIFY_ACK 0x0021 ↓:落库结果(m_ack_t,status=launcher 分配的通知 id nid);
|
||||
* - MSG_NOTIFY_DISMISS 0x0022 ↑:撤下通知(负载 uint32 nid)。
|
||||
*
|
||||
* 约定:只做「一次性发送 + 读 ACK」,不造 IPC 订阅循环;SDK 本地活动表(meow_ui_notify.c)
|
||||
* 保留(app 进程内即时查询/配额自检),真正的通知中心数据在 launcher 中央存储,二者以
|
||||
* 本接口经 IPC 同步。全部失败路径优雅降级(无 socket/写失败/超时 → 返回 0,不崩)。
|
||||
*
|
||||
* 本接口只暴露「SDK 类型出/进 + 状态码」,不把 m_notify_t / 帧格式泄漏给 meow_ui_notify.c;
|
||||
* 映射层(SDK 枚举 ↔ IPC 字符串/uint32)实现在 meow_notify_ipc.c 内部。
|
||||
*/
|
||||
#ifndef MEOW_NOTIFY_IPC_H
|
||||
#define MEOW_NOTIFY_IPC_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "meow_ui.h" /* meow_notify */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* 当前 app 的 app_id(env MEOW_LOG_APP,缺省 "app";pthread_once 缓存,线程安全)。 */
|
||||
const char *meow_notify_ipc_app_id(void);
|
||||
|
||||
/* 把一条 SDK meow_notify 映射为 m_notify_t 并一次性发送 MSG_NOTIFY,阻塞读
|
||||
* MSG_NOTIFY_ACK 取 launcher 分配的通知 id(nid)。replaces_id:0=新建;>0=更新已存在
|
||||
* 通知(与 freedesktop 语义一致)。成功返回 nid(>0);无 socket/写失败/超时/非 ACK →
|
||||
* 返回 0(调用方保留本地 id 兜底,不崩)。 */
|
||||
uint32_t meow_notify_ipc_send(const meow_notify *n, uint32_t replaces_id);
|
||||
|
||||
/* 一次性发送 MSG_NOTIFY_DISMISS 撤下 launcher 侧通知。best-effort:写失败/无 socket
|
||||
* 静默(不崩、不回读 ACK)。 */
|
||||
void meow_notify_ipc_dismiss(uint32_t nid);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* MEOW_NOTIFY_IPC_H */
|
||||
@@ -32,7 +32,7 @@ extern "C" {
|
||||
#define MEOW_KEY_VOLUMEDOWN 114
|
||||
#define MEOW_KEY_VOLUMEUP 115
|
||||
#define MEOW_KEY_PLAYPAUSE 164
|
||||
#define MEOW_KEY_PREVIOUS 19 /* KEY_PREVIOUSSONG */
|
||||
#define MEOW_KEY_PREVIOUS 165 /* KEY_PREVIOUSSONG=165(上一曲;19 是另一码,勿混用) */
|
||||
#define MEOW_KEY_NEXT 163 /* KEY_NEXTSONG */
|
||||
|
||||
/* 滑动方向(on_swipe 的 dir 参数) */
|
||||
|
||||
@@ -29,10 +29,14 @@
|
||||
#define FBIO_WAITFORVSYNC 0x4620
|
||||
|
||||
#define FB_DEV "/dev/fb0"
|
||||
#define MEOW_FONT_PNG_CARD "/usr/data/mnt/sd_0/MeowAPPs/fonts/font.png"
|
||||
#define MEOW_FONT_IDX_CARD "/usr/data/mnt/sd_0/MeowAPPs/fonts/font.idx"
|
||||
#define MEOW_FONT_PNG_CARD "/usr/data/mnt/sd_0/MeowOS/fonts/default/font.png"
|
||||
#define MEOW_FONT_IDX_CARD "/usr/data/mnt/sd_0/MeowOS/fonts/default/font.idx"
|
||||
#define MEOW_FONT_PNG_SYS "/usr/share/meowos/font/font.png"
|
||||
#define MEOW_FONT_IDX_SYS "/usr/share/meowos/font/font.idx"
|
||||
/* 默认字库(卡 MeowOS/fonts/default)与固件内置之外,launcher 会把「当前生效字体」
|
||||
* 经环境变量下发给 app(见 meow_launcherd.c launch_app / meow_font_effective_paths):
|
||||
* MEOW_FONT_PNG / MEOW_FONT_IDX = 绝对路径;MEOW_FONT_APP = <family> 或 "default"。
|
||||
* SDK load_font 优先读 env(匹配系统 font.file),无则回退卡默认 → 固件。 */
|
||||
|
||||
#define MUI_DEFAULT_W 480
|
||||
#define MUI_DEFAULT_H 720
|
||||
@@ -572,13 +576,55 @@ int meow_gfx_is_device(void) { return g_fb_mem != NULL; }
|
||||
int meow_gfx_mfont_ready(void) { return g_mfont_ready; }
|
||||
|
||||
/* ================= 初始化/销毁 ================= */
|
||||
/* 字库加载优先级(M2.x):
|
||||
* 1) 环境变量 MEOW_FONT_PNG/MEOW_FONT_IDX(launcher 注入的「当前生效字体」= 系统 font.file)
|
||||
* —— app 未自带字库(§5.7 load_handle 未实现)时跟随系统 font.file;
|
||||
* 2) 卡默认 MeowOS/fonts/default/font.png(不再硬编码 MeowAPPs/fonts);
|
||||
* 3) 固件 /usr/share/meowos/font/font.png。
|
||||
* 全失败 → g_mfont_ready=0,text_draw 回退 5x7 点阵(系统不死)。 */
|
||||
static int load_font(void)
|
||||
{
|
||||
const char *ep = getenv("MEOW_FONT_PNG");
|
||||
const char *ei = getenv("MEOW_FONT_IDX");
|
||||
if (ep && ep[0] && ei && ei[0] && meow_font_load(ep, ei) == 0) return 1;
|
||||
if (meow_font_load(MEOW_FONT_PNG_CARD, MEOW_FONT_IDX_CARD) == 0) return 1;
|
||||
if (meow_font_load(MEOW_FONT_PNG_SYS, MEOW_FONT_IDX_SYS) == 0) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* M2.5x 多字体:读 launcher 注入的环境变量,构建 6 字符类 → 字库的映射(同 family 去重、
|
||||
* ≤4 套上限由 meow_font_config_multifont 统一处理)。launcher 注入(见 meow_launcherd.c
|
||||
* launch_app):MEOW_MULTI_FONT=0/1;MEOW_FONT_PNG/IDX=全局(保留);每类
|
||||
* MEOW_FONT_CLS_<X>_PNG/IDX(命中则覆盖全局;未注入 = 该类跟随全局)。
|
||||
* 仅当全局字库已装载(g_mfont_ready)才配置;关/无 env → 单字体,零额外开销。 */
|
||||
static void meow_gfx_load_multifont(void)
|
||||
{
|
||||
static const char *cls_env[MEOW_FCLS_COUNT][2] = {
|
||||
{ "MEOW_FONT_CLS_CJK_PNG", "MEOW_FONT_CLS_CJK_IDX" },
|
||||
{ "MEOW_FONT_CLS_CJK_SYM_PNG", "MEOW_FONT_CLS_CJK_SYM_IDX" },
|
||||
{ "MEOW_FONT_CLS_NUM_PNG", "MEOW_FONT_CLS_NUM_IDX" },
|
||||
{ "MEOW_FONT_CLS_LATIN_PNG", "MEOW_FONT_CLS_LATIN_IDX" },
|
||||
{ "MEOW_FONT_CLS_LATIN_SYM_PNG", "MEOW_FONT_CLS_LATIN_SYM_IDX" },
|
||||
{ "MEOW_FONT_CLS_OTHER_PNG", "MEOW_FONT_CLS_OTHER_IDX" },
|
||||
};
|
||||
const char *emf = getenv("MEOW_MULTI_FONT");
|
||||
int on = emf && emf[0] == '1';
|
||||
const char *gp = getenv("MEOW_FONT_PNG");
|
||||
const char *gi = getenv("MEOW_FONT_IDX");
|
||||
meow_font_class_cfg cfg[MEOW_FCLS_COUNT];
|
||||
memset(cfg, 0, sizeof cfg);
|
||||
for (int c = 0; c < MEOW_FCLS_COUNT; c++) {
|
||||
const char *p = getenv(cls_env[c][0]);
|
||||
const char *idx = getenv(cls_env[c][1]);
|
||||
if (p && p[0] && idx && idx[0]) { cfg[c].png = p; cfg[c].idx = idx; }
|
||||
}
|
||||
int distinct = 0, truncated = 0;
|
||||
if (meow_font_config_multifont(on, gp, gi, cfg, &distinct, &truncated) != 0)
|
||||
meow_log(MEOW_LOG_WARN, "multi_font: config failed, single-font fallback");
|
||||
else if (truncated)
|
||||
meow_log(MEOW_LOG_WARN, "multi_font: >%d distinct fonts -> capped (more memory!)", distinct);
|
||||
}
|
||||
|
||||
int meow_ui_init(const meow_ui_opts *opts)
|
||||
{
|
||||
if (g_init_ok) return 0;
|
||||
@@ -623,6 +669,7 @@ int meow_ui_init(const meow_ui_opts *opts)
|
||||
|
||||
meow_font_set_fb((unsigned)g_xres, (unsigned)g_yres, (unsigned)g_line);
|
||||
g_mfont_ready = load_font();
|
||||
if (g_mfont_ready) meow_gfx_load_multifont(); /* M2.5x 多字体(读 env;关/无 env = 单字体) */
|
||||
|
||||
const char *dp = getenv("MEOW_UI_DUMP");
|
||||
if (dp && *dp) g_dump_path = strdup(dp);
|
||||
|
||||
@@ -219,9 +219,9 @@ static void *input_thread(void *arg)
|
||||
else if (!val && t_down) touch_release();
|
||||
} else if (code == MEOW_KEY_POWER || code == MEOW_KEY_VOLUMEDOWN ||
|
||||
code == MEOW_KEY_VOLUMEUP || code == MEOW_KEY_PLAYPAUSE ||
|
||||
code == MEOW_KEY_PREVIOUS || code == MEOW_KEY_NEXT || code == 165) {
|
||||
code == MEOW_KEY_PREVIOUS || code == MEOW_KEY_NEXT || code == 19) {
|
||||
/* M2.22(A)/M2.24 纪律:物理键(电源 116 / 音量 114,115 / 播放 164 /
|
||||
* 上一曲 19,165 / 下一曲 163)全部是系统语义 —— watchdog/launcher 统一消费,
|
||||
* 上一曲 165(MEOW_KEY_PREVIOUS)/19(旧码) / 下一曲 163)全部是系统语义 —— watchdog/launcher 统一消费,
|
||||
* 绝不下发 APP。应用不碰物理键、不把任何物理键映射成确认/exit/返回/翻页
|
||||
* (KEY_CONTROL_DESIGN §1.3 / APPS.md §3.4.1);APP 只接收 tap/swipe/tick。
|
||||
* 忽略该键,不发 MEOW_EV_KEY。 */
|
||||
|
||||
@@ -12,15 +12,20 @@
|
||||
* (替换);媒体类型单例(每 app 仅保留最近 1 条,重发替换——可多媒体 App)。
|
||||
* - 超时语义 [实现定案]:timeout_ms > 0 为该毫秒数后过期;0 = 不自动过期(直到
|
||||
* meow_notify_cancel);-1 = 常驻(ONGOING),同样不自动过期。
|
||||
* - 落点:本阶段内部维护活动通知表 + 用既有 meow_log() 打日志作为落点;真实投递到
|
||||
* 通知中心 UI / D-Bus 是后续任务——meow_notify 结构已对齐 freedesktop 字段
|
||||
* (summary/body/…)为此预留,但不写死后端。
|
||||
* - 落点(任务 #5 SDK↔IPC 桥):本表登记后,经 IPC 把 MSG_NOTIFY(0x0020) 送到
|
||||
* launcher 中央存储(meow_notif.c 的 meow_notif_post),回读 MSG_NOTIFY_ACK(0x0021)
|
||||
* 取 launcher 分配的通知 id(nid,存进 slot->ipc_id,供后续 replaces_id/dismiss 用)。
|
||||
* 通知中心 UI / D-Bus / TF 转储由 launcher 侧处理;SDK 本地活动表保留(app 进程内
|
||||
* 即时查询/配额自检)。无 socket/写失败/超时 → 优雅降级(保留本地 id 兜底,不崩)。
|
||||
* meow_notify 结构已对齐 freedesktop 字段(summary/body/…)为此预留。
|
||||
*
|
||||
* 线程安全:pthread_mutex(同 meow_ui_log.c,launcher 前后台进程都会在已链 -lpthread
|
||||
* 的应用里调用)。不依赖 /dev/fb0、不依赖板子,可宿主编译。内部只复用
|
||||
* meow_ui_time_ms()(单调毫秒)与 meow_log()(统一日志)两个既有 SDK 能力。
|
||||
* 的应用里调用),且 IPC 发送/读 ACK 在持锁之外进行(避免 socket I/O 阻塞其它查询)。
|
||||
* 不依赖 /dev/fb0、不依赖板子,可宿主编译。内部只复用 meow_ui_time_ms()(单调毫秒)、
|
||||
* meow_log()(统一日志)与 meow_notify_ipc_*(IPC 桥,纯传输层)三个既有/新增 SDK 能力。
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
#include "meow_notify_ipc.h" /* 任务 #5:通知 → launcher 中央存储的 IPC 桥(映射+传输) */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
@@ -32,7 +37,8 @@
|
||||
|
||||
/* 活动通知槽(内部;标题/正文等复制副本,不持 app 外部指针,避免栈字符串悬垂) */
|
||||
typedef struct {
|
||||
uint32_t id; /* 唯一 id;0=空闲槽 */
|
||||
uint32_t id; /* 本地唯一 id(app 侧句柄);0=空闲槽 */
|
||||
uint32_t ipc_id; /* launcher 中央存储分配的通知 id(nid);0=未同步/降级 */
|
||||
int active; /* 1=活动(未取消未过期),0=空闲 */
|
||||
meow_notify_category cat;
|
||||
meow_notify_urgency urg;
|
||||
@@ -85,6 +91,7 @@ static void notify_purge_expired(void)
|
||||
if (s->timeout_ms > 0 && (now - s->created_ms) >= (uint64_t)s->timeout_ms) {
|
||||
s->active = 0;
|
||||
s->id = 0;
|
||||
s->ipc_id = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,6 +100,7 @@ static void notify_purge_expired(void)
|
||||
static void notify_fill(notify_slot_t *s, const meow_notify *n, uint32_t id, uint64_t now)
|
||||
{
|
||||
s->id = id;
|
||||
s->ipc_id = 0; /* 初值=未同步;发送后由 notify_deliver 回填 launcher 分配的 nid */
|
||||
s->active = 1;
|
||||
s->cat = n->cat;
|
||||
s->urg = ((int)n->urgency >= 0 && (int)n->urgency < (int)MEOW_NOTIFY_URG_COUNT)
|
||||
@@ -106,12 +114,23 @@ static void notify_fill(notify_slot_t *s, const meow_notify *n, uint32_t id, uin
|
||||
s->progress = (n->progress < -1) ? -1 : (n->progress > 100 ? 100 : n->progress);
|
||||
}
|
||||
|
||||
/* 落点:本阶段用统一日志标出一条投递;真实后端(通知中心 UI / D-Bus)后续替换此处。 */
|
||||
static void notify_deliver(const notify_slot_t *s, const char *ev)
|
||||
/* 落点(任务 #5):把通知经 IPC MSG_NOTIFY(0x0020) 送到 launcher 中央存储
|
||||
* (meow_notif.c 的 meow_notif_post),回读 MSG_NOTIFY_ACK(0x0021) 取 launcher 分配的
|
||||
* 通知 id(nid)。返回 nid(>0=成功);无 socket/写失败/超时 → 返回 0(调用方保留本地 id
|
||||
* 兜底,不崩)。调用方须在持锁外调用(socket I/O 不占 g_mu,避免阻塞其它查询)。
|
||||
* replaces_id:0=新建;>0=更新 launcher 侧已存在通知(freedesktop 语义)。 */
|
||||
static uint32_t notify_deliver(const meow_notify *n, uint32_t replaces_id)
|
||||
{
|
||||
meow_log(MEOW_LOG_INFO, "[notify] %s id=%u type=%s urgency=%s title=%s",
|
||||
ev, (unsigned)s->id, meow_notify_category_str(s->cat),
|
||||
meow_notify_urgency_str(s->urg), s->title);
|
||||
uint32_t nid = meow_notify_ipc_send(n, replaces_id);
|
||||
if (nid) {
|
||||
meow_log(MEOW_LOG_INFO, "[notify] delivered nid=%u replaces=%u cat=%s title=%s",
|
||||
(unsigned)nid, (unsigned)replaces_id, meow_notify_category_str(n->cat),
|
||||
n->title ? n->title : "");
|
||||
} else {
|
||||
meow_log(MEOW_LOG_INFO, "[notify] degraded (no launcher) cat=%s title=%s",
|
||||
meow_notify_category_str(n->cat), n->title ? n->title : "");
|
||||
}
|
||||
return nid;
|
||||
}
|
||||
|
||||
/* ============================================================ */
|
||||
@@ -132,7 +151,7 @@ int meow_notify_register_type(meow_notify_category cat)
|
||||
|
||||
uint32_t meow_notify_send(const meow_notify *n)
|
||||
{
|
||||
uint32_t id;
|
||||
uint32_t id, nid, replaces_id = 0;
|
||||
uint64_t now, oldest;
|
||||
int i, slot_idx, count, oldest_idx;
|
||||
|
||||
@@ -155,11 +174,16 @@ uint32_t meow_notify_send(const meow_notify *n)
|
||||
if (n->cat == MEOW_NOTIFY_CAT_MEDIA) {
|
||||
/* 媒体单例:复用已存在的媒体槽(重发替换同 app 上一条媒体通知) */
|
||||
for (i = 0; i < MEOW_NOTIFY_MAX_ACTIVE; i++)
|
||||
if (g_slots[i].active && g_slots[i].cat == MEOW_NOTIFY_CAT_MEDIA) { slot_idx = i; break; }
|
||||
if (g_slots[i].active && g_slots[i].cat == MEOW_NOTIFY_CAT_MEDIA) {
|
||||
slot_idx = i;
|
||||
replaces_id = g_slots[i].ipc_id; /* 更新该媒体通知(0=降级/未同步) */
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
count = notify_count_cat(n->cat);
|
||||
if (count >= MEOW_NOTIFY_MAX_PER_TYPE) {
|
||||
/* 满则丢该类型最旧一条(替换) */
|
||||
/* 满则丢该类型最旧一条(更新):先取被丢最旧的 launcher nid 作 replaces_id,
|
||||
* 再释放槽(顺序不可反,否则 ipc_id 已被清 0)。 */
|
||||
oldest_idx = -1;
|
||||
oldest = UINT64_MAX;
|
||||
for (i = 0; i < MEOW_NOTIFY_MAX_ACTIVE; i++)
|
||||
@@ -167,7 +191,13 @@ uint32_t meow_notify_send(const meow_notify *n)
|
||||
oldest = g_slots[i].created_ms;
|
||||
oldest_idx = i;
|
||||
}
|
||||
if (oldest_idx >= 0) { g_slots[oldest_idx].active = 0; g_slots[oldest_idx].id = 0; }
|
||||
if (oldest_idx >= 0) {
|
||||
replaces_id = g_slots[oldest_idx].ipc_id; /* 复用被丢最旧的 launcher nid */
|
||||
if (replaces_id == 0) replaces_id = 0; /* 未同步时按新建处理 */
|
||||
g_slots[oldest_idx].active = 0;
|
||||
g_slots[oldest_idx].id = 0;
|
||||
g_slots[oldest_idx].ipc_id = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (slot_idx < 0) {
|
||||
@@ -184,26 +214,42 @@ uint32_t meow_notify_send(const meow_notify *n)
|
||||
id = g_next_id++;
|
||||
if (g_next_id == 0) g_next_id = 1; /* 防回绕 */
|
||||
notify_fill(&g_slots[slot_idx], n, id, now);
|
||||
notify_deliver(&g_slots[slot_idx], "send");
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
|
||||
/* 落点(持锁外):IPC 送 launcher 中央存储,回读 nid;无 launcher 则降级(nid=0)。 */
|
||||
nid = notify_deliver(n, replaces_id);
|
||||
|
||||
/* 回填 launcher 分配的 nid(槽可能已被并发 cancel/过期清掉——仅在时回填)。 */
|
||||
if (nid) {
|
||||
pthread_mutex_lock(&g_mu);
|
||||
for (i = 0; i < MEOW_NOTIFY_MAX_ACTIVE; i++)
|
||||
if (g_slots[i].active && g_slots[i].id == id) { g_slots[i].ipc_id = nid; break; }
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
int meow_notify_cancel(uint32_t id)
|
||||
{
|
||||
int i, rc = 0;
|
||||
uint32_t ipc_id = 0;
|
||||
if (id == 0) return 0;
|
||||
pthread_mutex_lock(&g_mu);
|
||||
notify_purge_expired();
|
||||
for (i = 0; i < MEOW_NOTIFY_MAX_ACTIVE; i++) {
|
||||
if (g_slots[i].active && g_slots[i].id == id) {
|
||||
ipc_id = g_slots[i].ipc_id; /* launcher 侧 nid;0=未同步/降级 */
|
||||
g_slots[i].active = 0;
|
||||
g_slots[i].id = 0;
|
||||
g_slots[i].ipc_id = 0;
|
||||
rc = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
/* 落点:经 IPC MSG_NOTIFY_DISMISS(0x0022) 撤下 launcher 中央存储对应通知
|
||||
* (best-effort;无 launcher/未同步则静默降级)。 */
|
||||
if (rc && ipc_id) meow_notify_ipc_dismiss(ipc_id);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
+15
-18
@@ -14,6 +14,7 @@
|
||||
*/
|
||||
#include "meow_ui_sys.h"
|
||||
#include "meow_ui_internal.h"
|
||||
#include "meow_ui_vol.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -23,7 +24,6 @@
|
||||
#include <time.h>
|
||||
|
||||
#define MEOW_SETTINGS_FILE "/usr/data/meow/settings.conf"
|
||||
#define VOL_DEFAULT 60
|
||||
|
||||
/* 蓝牙 powered 缓存刷新间隔(hciconfig 较贵) */
|
||||
#define BT_REFRESH_MS 30000
|
||||
@@ -80,22 +80,15 @@ static int hci_powered(void)
|
||||
return up;
|
||||
}
|
||||
|
||||
/* 音量:读 settings.conf 的 audio.volume(launcher 同源,watchdog 写同文件)。 */
|
||||
/* 音量:读 settings.conf 的 audio.volume(launcher 同源,watchdog 写同文件)。
|
||||
* 与 launcher 口径一致:0-100,且尊重 audio.volume_max 上限 clamp(vol_clamp 同义)。
|
||||
* 注意 settings 落盘为 "key = value"(带空格),必须按 '=' 切分解析,不能朴素前缀匹配——
|
||||
* 否则永远读不到(回退默认 60,即「状态栏音量数字不对、卡在 60」的根因)。 */
|
||||
static int settings_vol(void)
|
||||
{
|
||||
FILE *f = fopen(MEOW_SETTINGS_FILE, "r");
|
||||
if (!f) return VOL_DEFAULT;
|
||||
int vol = VOL_DEFAULT;
|
||||
char buf[128];
|
||||
while (fgets(buf, sizeof buf, f)) {
|
||||
if (!strncmp(buf, "audio.volume=", 13)) {
|
||||
int v = atoi(buf + 13);
|
||||
if (v >= 0 && v <= 100) vol = v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
return vol;
|
||||
int vol = meow_ui_vol_read(MEOW_SETTINGS_FILE, "audio.volume", MEOW_UI_VOL_DEFAULT);
|
||||
int vol_max = meow_ui_vol_read(MEOW_SETTINGS_FILE, "audio.volume_max", MEOW_UI_VOL_MAX_DEF);
|
||||
return meow_ui_vol_clamp(vol, vol_max);
|
||||
}
|
||||
|
||||
int meow_ui_sys_init(void)
|
||||
@@ -210,21 +203,25 @@ void meow_ui_sys_statusbar_render(void)
|
||||
/* 整条清底:覆盖 app 可能画进顶行的内容(z 最顶) */
|
||||
meow_ui_fill_rect(0, 0, w, h, th->bg);
|
||||
|
||||
/* 文字垂直锚 y:(栏高-行高)/2 居中;下限 1 防极端行高 > 栏高时负 y 溢出顶端(不越界优先)。 */
|
||||
int ty = (h - meow_ui_text_height(MUI_SBAR_FONT)) / 2;
|
||||
if (ty < 1) ty = 1;
|
||||
|
||||
/* 时钟(左):每秒现取 localtime */
|
||||
char timebuf[16];
|
||||
time_t t = time(NULL);
|
||||
struct tm tm;
|
||||
localtime_r(&t, &tm);
|
||||
snprintf(timebuf, sizeof timebuf, "%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec);
|
||||
meow_ui_text(8, (h - meow_ui_text_height(MUI_SBAR_FONT)) / 2, timebuf, MUI_SBAR_FONT, th->fg);
|
||||
meow_ui_text(8, ty, timebuf, MUI_SBAR_FONT, th->fg);
|
||||
|
||||
/* 右侧信息串:batt% WIFI 蓝牙 VOL nn% */
|
||||
char right[96];
|
||||
statusbar_right_str(right, sizeof right);
|
||||
if (right[0])
|
||||
meow_ui_text_right(w - 8, (h - meow_ui_text_height(MUI_SBAR_FONT)) / 2, right, MUI_SBAR_FONT, th->fg);
|
||||
meow_ui_text_right(w - 8, ty, right, MUI_SBAR_FONT, th->fg);
|
||||
|
||||
/* 分隔线(栏内最底 1px,主题次要色);内容 inset=statusbar_height()=40 从 y=40 起即干净 */
|
||||
/* 分隔线(栏内最底 1px,主题次要色);内容 inset=statusbar_height()=28 从 y=28 起即干净 */
|
||||
meow_ui_fill_rect(0, h - 1, w, 1, th->dim);
|
||||
|
||||
g_statusbar_force = 0;
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
* - 电量 /sys/class/power_supply/battery/{capacity,status}
|
||||
* - WiFi /sys/class/net/wlan0/operstate
|
||||
* - 蓝牙 实际 powered(hciconfig -a hci0 的 UP;spawn 贵 → 缓存 30s 刷一次)
|
||||
* - 音量 /usr/data/meow/settings.conf 的 audio.volume(launcher 同文件)
|
||||
* - 音量 /usr/data/meow/settings.conf 的 audio.volume(launcher 同文件;
|
||||
* 0-100 并 clamp 到 audio.volume_max 上限,与 launcher vol_clamp 口径一致)
|
||||
* - 时钟 localtime_r(每秒在绘制时现取)
|
||||
*/
|
||||
#ifndef MEOW_UI_SYS_H
|
||||
@@ -28,9 +29,10 @@ typedef struct meow_sys_state {
|
||||
int vol; /* 音量 0-100 */
|
||||
} meow_sys_state;
|
||||
|
||||
/* 顶部状态栏几何(与 launcher SB_BAR_H=40 / SB_FONT=28 对齐,视觉统一)。 */
|
||||
#define MUI_SBAR_H 40
|
||||
#define MUI_SBAR_FONT 28
|
||||
/* 顶部状态栏几何(与 launcher SB_BAR_H=28 / SB_FONT=20 对齐,视觉统一)。
|
||||
* 高度 40→28(用户定),字号 28→20(28px 栏放不下 28 号;20 号按 (高-行高)/2 垂直居中)。 */
|
||||
#define MUI_SBAR_H 28
|
||||
#define MUI_SBAR_FONT 20
|
||||
|
||||
/* 首次全量读取;失败仍返回 0(字段置 -1/0)。 */
|
||||
int meow_ui_sys_init(void);
|
||||
@@ -44,7 +46,7 @@ const meow_sys_state *meow_ui_sys_get(void);
|
||||
/* 状态栏是否应绘制(= set_statusbar(1) 且 非 fullscreen)。 */
|
||||
int meow_ui_sys_statusbar_active(void);
|
||||
|
||||
/* 状态栏高度(px):active 时 40,否则 0(供 meow_ui_statusbar_height 用)。 */
|
||||
/* 状态栏高度(px):active 时 28,否则 0(供 meow_ui_statusbar_height 用)。 */
|
||||
int meow_ui_sys_statusbar_height(void);
|
||||
|
||||
/* 自上次渲染后状态栏数据是否已变(1s 轻刷/显隐切换置位);读取即清除。
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* meow_ui_vol.c — SDK 状态栏音量换算纯函数 + settings.conf 读取(A-0 核心数源)。
|
||||
*
|
||||
* 单独成 TU 的目的:
|
||||
* 1. 纯函数(clamp / 键读取)不依赖 meow_ui 其它子系统,宿主 gcc 直接可测
|
||||
* (test_meow_ui_vol.c),无需链整库/stub。
|
||||
* 2. 把「音量从 settings 读数并 clamp 到 volume_max」这一语义钉死在单点,
|
||||
* 与 launcher 的 vol_clamp(meow_launcherd.c)口径对齐,杜绝两端音量漂移。
|
||||
*
|
||||
* 数据源:/usr/data/meow/settings.conf 扁平 key=value。launcher meow_settings.c
|
||||
* 落盘为 "key = value"(带空格),本解析兼容无空格/多空格,与 launcher 读循环
|
||||
* 及 sdk meow_ui_theme.c 的 get_setting_str 同款解析。
|
||||
*/
|
||||
#include "meow_ui_vol.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
int meow_ui_vol_clamp(int vol, int vol_max)
|
||||
{
|
||||
if (vol < 0) vol = 0;
|
||||
if (vol > MEOW_UI_VOL_MAX) vol = MEOW_UI_VOL_MAX;
|
||||
if (vol_max < 0) vol_max = 0; /* 异常上限兜底:不进负值 */
|
||||
if (vol > vol_max) vol = vol_max;
|
||||
return vol;
|
||||
}
|
||||
|
||||
int meow_ui_vol_read(const char *path, const char *key, int def)
|
||||
{
|
||||
if (!path || !key) return def;
|
||||
FILE *f = fopen(path, "r");
|
||||
if (!f) return def;
|
||||
int kl = (int)strlen(key);
|
||||
char line[160];
|
||||
char *found = NULL;
|
||||
while (fgets(line, sizeof line, f)) {
|
||||
char *eq = strchr(line, '=');
|
||||
if (!eq) continue; /* 非 key=value 行(注释/空行)跳过 */
|
||||
char *k = line;
|
||||
while (*k == ' ' || *k == '\t') k++; /* key 左去空白 */
|
||||
int n = (int)(eq - k);
|
||||
while (n > 0 && (k[n-1] == ' ' || k[n-1] == '\t')) n--; /* key 右去空白 */
|
||||
if (n != kl || strncmp(k, key, (size_t)kl) != 0) continue; /* key 不精确匹配 */
|
||||
found = eq + 1; /* 命中所求键 */
|
||||
break;
|
||||
}
|
||||
fclose(f);
|
||||
if (!found) return def;
|
||||
while (*found == ' ' || *found == '\t') found++; /* value 左去空白;atoi 天然停于非数字 */
|
||||
return atoi(found);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* meow_ui_vol.h — SDK 状态栏音量换算/读取(meow_ui_vol.c)。
|
||||
* 不随 meow_ui.h 发布给应用;仅供 meow_ui_sys.c 与宿主单测(test_meow_ui_vol.c)使用。
|
||||
*/
|
||||
#ifndef MEOW_UI_VOL_H
|
||||
#define MEOW_UI_VOL_H
|
||||
|
||||
/* 音量语义常量(与 launcher MEOW_VOL_MAX/DEFAULT、meow_settings 表逐一致):
|
||||
* audio.volume 默认 60,取值域 0-100(硬上限 MEOW_UI_VOL_MAX);
|
||||
* audio.volume_max 默认 100(软上限,clamp 上限,用户可调小);
|
||||
* audio.volume_step 默认 1(跳步;只影响「每次音量键改多少」,与状态栏展示无关——
|
||||
* SDK 只读当前值,步进由改动方(launcher)承担)。 */
|
||||
#define MEOW_UI_VOL_DEFAULT 60
|
||||
#define MEOW_UI_VOL_MAX 100
|
||||
#define MEOW_UI_VOL_MAX_DEF 100
|
||||
|
||||
/* 纯函数:把软件音量夹到 [0, MEOW_UI_VOL_MAX],再夹到 volume_max 软上限。
|
||||
* 语义与 launcher 的 vol_clamp(meow_launcherd.c)一致:
|
||||
* - 硬上限 100(0-100 口径);
|
||||
* - 尊重 audio.volume_max(用户把上限调小则音量被夹到该值);
|
||||
* - volume_max 异常(<0)兜底为 0(不进负值)。
|
||||
* 无文件/全局依赖,宿主 gcc 可直接测。 */
|
||||
int meow_ui_vol_clamp(int vol, int vol_max);
|
||||
|
||||
/* 读 settings.conf 里一个 int 键。扁平 "key=value" 格式(launcher meow_settings.c
|
||||
* 落盘为 "key = value",带空格;这里兼容无空格/多空格两种写式)。
|
||||
* 解析:'=' 切分,key/value 去首尾空白,key 精确匹配(避免 audio.volume 误配
|
||||
* audio.volume_max)。未命中/打开失败/键缺失回退 def。
|
||||
* path 由调用方传入(设备传导 MEOW_SETTINGS_FILE;宿主单测指向临时文件)。 */
|
||||
int meow_ui_vol_read(const char *path, const char *key, int def);
|
||||
|
||||
#endif /* MEOW_UI_VOL_H */
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* test_meow_ui_vol.c — 宿主单元测试:SDK 状态栏音量换算纯函数 + settings.conf 读取。
|
||||
*
|
||||
* 直接编译 meow_ui_vol.c(纯函数,无 meow_ui 依赖),用临时 settings 文件验证:
|
||||
* V1 解析:launcher 落盘 "key = value"(带空格)形式能正确读到值(根因回归——
|
||||
* 旧代码朴素前缀匹配 "audio.volume=" 永远读不到 → 卡默认 60);
|
||||
* V2 解析:无空格 "key=value" / 多空格 / 头注释行 / 键缺失回退默认 / 精确匹配
|
||||
* (audio.volume 不误配 audio.volume_max);
|
||||
* V3 clamp:volume_max 上限、硬上限 100、负值兜底 0、上限异常(<0)兜底 0;
|
||||
* V4 管道(镜像 meow_ui_sys.c settings_vol):读 + clamp 到 volume_max;
|
||||
* V5 展示串:与 meow_ui_sys.c statusbar_right_str 同款 "VOL %d%%",断言显示值即
|
||||
* 当前夹后音量(含 step 语义:a.volume_step 只决定步进,展示原样不/舍入)。
|
||||
*
|
||||
* 编译(宿主 gcc,mipsel 无关):
|
||||
* gcc -o test_meow_ui_vol test_meow_ui_vol.c meow_ui_vol.c -I.
|
||||
*/
|
||||
#include "meow_ui_vol.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int failures = 0;
|
||||
static void check(const char *name, int cond)
|
||||
{
|
||||
printf("%s %s\n", cond ? "PASS" : "FAIL", name);
|
||||
if (!cond) failures++;
|
||||
}
|
||||
|
||||
/* 写一份临时 settings 文件(lines 为已含 '\n' 的行内容;NULL 结束) */
|
||||
static const char *tmp_path;
|
||||
static void write_settings(const char **lines)
|
||||
{
|
||||
FILE *f = fopen(tmp_path, "w");
|
||||
if (!f) { perror("fopen"); exit(2); }
|
||||
for (int i = 0; lines[i]; i++) fputs(lines[i], f);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
/* 镜像 meow_ui_sys.c 的 settings_vol():读 audio.volume + volume_max,再 clamp。 */
|
||||
static int settings_vol_of(const char *path)
|
||||
{
|
||||
int vol = meow_ui_vol_read(path, "audio.volume", MEOW_UI_VOL_DEFAULT);
|
||||
int vol_max = meow_ui_vol_read(path, "audio.volume_max", MEOW_UI_VOL_MAX_DEF);
|
||||
return meow_ui_vol_clamp(vol, vol_max);
|
||||
}
|
||||
|
||||
/* 状态栏右侧 VOL 串组装(与 meow_ui_sys.c statusbar_right_str 同款)。 */
|
||||
static void vol_display(int vol, int vol_max, char *out, size_t n)
|
||||
{
|
||||
snprintf(out, n, "VOL %d%%", meow_ui_vol_clamp(vol, vol_max));
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
char base[64];
|
||||
snprintf(base, sizeof base, "/tmp/meow_vol_%d.conf", (int)getpid());
|
||||
tmp_path = base;
|
||||
|
||||
/* ---- V1 解析:launcher 落盘格式 "key = value"(带空格)——根因回归 ---- */
|
||||
{
|
||||
const char *L[] = { "# meow-settings 1\n", "audio.volume = 82\n", NULL };
|
||||
write_settings(L);
|
||||
check("V1 launcher 空格格式 audio.volume=82 -> 82",
|
||||
meow_ui_vol_read(tmp_path, "audio.volume", -1) == 82);
|
||||
}
|
||||
|
||||
/* ---- V2 解析:无空格 / 多空格 / 注释 / 缺键 / 精确匹配 ---- */
|
||||
{
|
||||
const char *L[] = { "audio.volume=40\n", NULL };
|
||||
write_settings(L);
|
||||
check("V2 无空格 audio.volume=40 -> 40",
|
||||
meow_ui_vol_read(tmp_path, "audio.volume", -1) == 40);
|
||||
|
||||
const char *L2[] = { " audio.volume = 7 \n", NULL };
|
||||
write_settings(L2);
|
||||
check("V2 多空格 audio.volume = 7 -> 7",
|
||||
meow_ui_vol_read(tmp_path, "audio.volume", -1) == 7);
|
||||
|
||||
const char *L3[] = { "display.brightness = 100\n", NULL };
|
||||
write_settings(L3);
|
||||
check("V2 键缺失 -> 默认 -1", meow_ui_vol_read(tmp_path, "audio.volume", -1) == -1);
|
||||
|
||||
const char *L4[] = { "audio.volume_max = 90\n", NULL };
|
||||
write_settings(L4);
|
||||
check("V2 audio.volume 不误配 audio.volume_max -> 默认",
|
||||
meow_ui_vol_read(tmp_path, "audio.volume", -1) == -1);
|
||||
check("V2 单独读 audio.volume_max=90 -> 90",
|
||||
meow_ui_vol_read(tmp_path, "audio.volume_max", -1) == 90);
|
||||
}
|
||||
|
||||
/* ---- V3 clamp 纯函数 ---- */
|
||||
check("V3 clamp(60,100)=60", meow_ui_vol_clamp(60, 100) == 60);
|
||||
check("V3 clamp(80,60)=60 (尊重 volume_max)", meow_ui_vol_clamp(80, 60) == 60);
|
||||
check("V3 clamp(80,100)=80", meow_ui_vol_clamp(80, 100) == 80);
|
||||
check("V3 clamp(150,100)=100 (硬上限)", meow_ui_vol_clamp(150, 100) == 100);
|
||||
check("V3 clamp(150,80)=80", meow_ui_vol_clamp(150, 80) == 80);
|
||||
check("V3 clamp(-5,100)=0 (负值兜底)", meow_ui_vol_clamp(-5, 100) == 0);
|
||||
check("V3 clamp(60,0)=0 (上限 0)", meow_ui_vol_clamp(60, 0) == 0);
|
||||
check("V3 clamp(60,-10)=0 (上限异常兜底)", meow_ui_vol_clamp(60, -10) == 0);
|
||||
check("V3 clamp(120,255)=100 (硬上限优先于大上限)", meow_ui_vol_clamp(120, 255) == 100);
|
||||
|
||||
/* ---- V4 管道:读 + clamp 到 volume_max(镜像 settings_vol)---- */
|
||||
{
|
||||
const char *L[] = { "audio.volume = 82\n", "audio.volume_max = 100\n", NULL };
|
||||
write_settings(L);
|
||||
check("V4 vol=82 max=100 -> 82", settings_vol_of(tmp_path) == 82);
|
||||
|
||||
const char *L2[] = { "audio.volume = 82\n", "audio.volume_max = 60\n", NULL };
|
||||
write_settings(L2);
|
||||
check("V4 vol=82 max=60 -> 60 (clamp)", settings_vol_of(tmp_path) == 60);
|
||||
|
||||
const char *L3[] = { "audio.volume = 150\n", "audio.volume_max = 100\n", NULL };
|
||||
write_settings(L3);
|
||||
check("V4 vol=150 max=100 -> 100 (硬上限)", settings_vol_of(tmp_path) == 100);
|
||||
|
||||
const char *L4[] = { "audio.volume = -3\n", "audio.volume_max = 100\n", NULL };
|
||||
write_settings(L4);
|
||||
check("V4 vol=-3 -> 0", settings_vol_of(tmp_path) == 0);
|
||||
|
||||
const char *L5[] = { "display.brightness = 100\n", NULL }; /* 无音量键 */
|
||||
write_settings(L5);
|
||||
check("V4 无 audio.volume -> 默认 60", settings_vol_of(tmp_path) == MEOW_UI_VOL_DEFAULT);
|
||||
}
|
||||
|
||||
/* ---- V5 展示串:VOL %d%(step 只决定步进,展示原样不/舍入)---- */
|
||||
{
|
||||
char s[16];
|
||||
vol_display(60, 100, s, sizeof s);
|
||||
check("V5 vol=60 -> \"VOL 60%\"", !strcmp(s, "VOL 60%"));
|
||||
vol_display(80, 60, s, sizeof s);
|
||||
check("V5 vol=80 max=60 -> \"VOL 60%\" (显示夹后值)", !strcmp(s, "VOL 60%"));
|
||||
vol_display(-5, 100, s, sizeof s);
|
||||
check("V5 vol=-5 -> \"VOL 0%\"", !strcmp(s, "VOL 0%"));
|
||||
vol_display(37, 100, s, sizeof s);
|
||||
check("V5 vol=37 -> \"VOL 37%\" (step 值原样展示)", !strcmp(s, "VOL 37%"));
|
||||
}
|
||||
|
||||
unlink(tmp_path);
|
||||
|
||||
printf("\n%s (%d failure%s)\n", failures ? "RESULT: FAIL" : "RESULT: PASS",
|
||||
failures, failures == 1 ? "" : "s");
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* test_notify_integration.c — 宿主集成测试:meow_notify_send/cancel 全链路(任务 #5)。
|
||||
*
|
||||
* 直接把 meow_ui_notify.c + meow_notify_ipc.c 编进来,仅 stub 两个 SDK 能力
|
||||
* (meow_log 空实现 / meow_ui_time_ms 单调毫秒),用本地 AF_UNIX stub socket 回环:
|
||||
*
|
||||
* S1 send(general,timeout=0) → 返回本地 id(1),stub 回 nid=101,active_count=1;
|
||||
* S2 send(media) → media=1、category=media(经 stub 捕获帧校验);
|
||||
* S3 同类型再发(未满配额)→ 返回本地 id(3);
|
||||
* S4 cancel(本地id=1) → 返回 1,stub 收到 MSG_NOTIFY_DISMISS(负载 nid=101);
|
||||
* S5 配额满(每类型4)后发第 5 条 → 丢最旧:该 5 条 send 的 replaces_id == 被丢最旧的 nid;
|
||||
* S6 无 launcher(connect 失败)→ send 仍返回本地 id(>0)、不崩;cancel 也静默不崩。
|
||||
*
|
||||
* 编译(宿主 gcc):
|
||||
* gcc -o test_notify_integration test_notify_integration.c \
|
||||
* meow_ui_notify.c meow_notify_ipc.c -I. -I../meow_launcherd -lpthread
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
#include "meow_notify_ipc.h"
|
||||
#include "meow_ipc.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <pthread.h>
|
||||
|
||||
/* ---- stub SDK 依赖(避免链整库) ---- */
|
||||
void meow_log(meow_log_level lvl, const char *fmt, ...) { (void)lvl; (void)fmt; }
|
||||
uint64_t meow_ui_time_ms(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
|
||||
}
|
||||
|
||||
/* ---- stub server 捕获(每次连接一帧) ---- */
|
||||
#define MAXCAP 16
|
||||
typedef struct { int type; m_notify_t n; uint32_t dismiss_id; uint32_t nid; } srec_t;
|
||||
static srec_t g_rec[MAXCAP];
|
||||
static int g_nrec = 0;
|
||||
static int g_stop = 0;
|
||||
static int g_listen_fd = -1;
|
||||
static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
static void *server(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
while (!g_stop) {
|
||||
int c = accept(g_listen_fd, NULL, NULL);
|
||||
if (c < 0) {
|
||||
if (errno == EAGAIN && g_stop) break; /* 非阻塞:listener 被关闭 */
|
||||
if (errno == EBADF) break;
|
||||
usleep(1000);
|
||||
continue;
|
||||
}
|
||||
uint8_t hdr[MEOW_IPC_HDR_LEN];
|
||||
size_t off = 0;
|
||||
while (off < sizeof hdr) { ssize_t r = read(c, hdr + off, sizeof hdr - off); if (r <= 0) break; off += (size_t)r; }
|
||||
if (off < sizeof hdr) { close(c); continue; }
|
||||
uint32_t type, len, req_id;
|
||||
memcpy(&req_id, hdr + 8, 4);
|
||||
memcpy(&len, hdr + 12, 4);
|
||||
memcpy(&type, hdr + 6, 2);
|
||||
if (len > MEOW_IPC_MAX) { close(c); continue; }
|
||||
uint8_t pay[MEOW_IPC_MAX];
|
||||
off = 0;
|
||||
while (off < len) { ssize_t r = read(c, pay + off, len - off); if (r <= 0) break; off += (size_t)r; }
|
||||
|
||||
pthread_mutex_lock(&g_mu);
|
||||
int idx = g_nrec < MAXCAP ? g_nrec++ : (MAXCAP - 1);
|
||||
memset(&g_rec[idx], 0, sizeof g_rec[idx]);
|
||||
g_rec[idx].type = (int)type;
|
||||
if (type == MSG_NOTIFY && len >= sizeof(m_notify_t)) memcpy(&g_rec[idx].n, pay, sizeof(m_notify_t));
|
||||
if (type == MSG_NOTIFY_DISMISS && len >= 4) memcpy(&g_rec[idx].dismiss_id, pay, 4);
|
||||
/* 分配 nid = 100 + NOTIFY 序号 */
|
||||
uint32_t seq = 0;
|
||||
for (int i = 0; i <= idx; i++) if (g_rec[i].type == MSG_NOTIFY) seq++;
|
||||
g_rec[idx].nid = (type == MSG_NOTIFY) ? (100 + seq) : 0;
|
||||
uint32_t ack_nid = g_rec[idx].nid;
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
|
||||
if (type == MSG_NOTIFY || type == MSG_NOTIFY_DISMISS) {
|
||||
m_ack_t a; memset(&a, 0, sizeof a); a.code = MEOW_OK; a.status = ack_nid;
|
||||
uint8_t rhdr[MEOW_IPC_HDR_LEN];
|
||||
uint32_t mag = MEOW_IPC_MAGIC; uint16_t ver = MEOW_IPC_VERSION, t = (uint16_t)
|
||||
(type == MSG_NOTIFY ? MSG_NOTIFY_ACK : MSG_ACK); uint32_t rl = sizeof a;
|
||||
memcpy(rhdr+0,&mag,4); memcpy(rhdr+4,&ver,2); memcpy(rhdr+6,&t,2); memcpy(rhdr+8,&req_id,4); memcpy(rhdr+12,&rl,4);
|
||||
size_t w0 = 0; while (w0 < sizeof rhdr) { ssize_t w = write(c, rhdr+w0, sizeof rhdr-w0); if (w<=0) break; w0 += (size_t)w; }
|
||||
size_t w1 = 0; while (w1 < rl) { ssize_t w = write(c, (uint8_t*)&a+w1, rl-w1); if (w<=0) break; w1 += (size_t)w; }
|
||||
}
|
||||
close(c);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int g_listen_made = 0;
|
||||
static int make_listen(char *path, size_t n)
|
||||
{
|
||||
snprintf(path, n, "/tmp/meow_notify_intg_%d.sock", (int)getpid());
|
||||
unlink(path);
|
||||
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
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 (bind(fd, (struct sockaddr*)&a, sizeof a) < 0 || listen(fd, 4) < 0) { close(fd); return -1; }
|
||||
fcntl(fd, F_SETFL, O_NONBLOCK); /* 非阻塞 listener:accept() 可被 g_stop 截断 */
|
||||
g_listen_fd = fd; g_listen_made = 1;
|
||||
return 0;
|
||||
}
|
||||
static void stop_server(void)
|
||||
{
|
||||
if (g_listen_made) { g_stop = 1; close(g_listen_fd); g_listen_made = 0; }
|
||||
}
|
||||
|
||||
static srec_t rec_at(int i)
|
||||
{
|
||||
srec_t r; pthread_mutex_lock(&g_mu); r = g_rec[i]; pthread_mutex_unlock(&g_mu); return r;
|
||||
}
|
||||
|
||||
static int failures = 0;
|
||||
static void check(const char *name, int cond)
|
||||
{
|
||||
printf("%s %s\n", cond ? "PASS" : "FAIL", name);
|
||||
if (!cond) failures++;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
setenv("MEOW_LOG_APP", "intgapp", 1);
|
||||
char sock[108];
|
||||
if (make_listen(sock, sizeof sock) != 0) { fprintf(stderr, "cannot listen\n"); return 2; }
|
||||
setenv("MEOW_IPC_SOCK", sock, 1);
|
||||
pthread_t th; pthread_create(&th, NULL, server, NULL);
|
||||
|
||||
/* S1 通用通知 */
|
||||
meow_notify nt; memset(&nt, 0, sizeof nt);
|
||||
nt.cat = MEOW_NOTIFY_CAT_GENERAL; nt.urgency = MEOW_NOTIFY_URG_NORMAL;
|
||||
nt.title = "t1"; nt.timeout_ms = 0;
|
||||
uint32_t id1 = meow_notify_send(&nt);
|
||||
check("S1 send 返回本地 id=1", id1 == 1);
|
||||
check("S1 active_count=1", meow_notify_active_count() == 1);
|
||||
check("S1 stub 回 nid=101", rec_at(0).nid == 101);
|
||||
|
||||
/* S2 媒体通知:单例 + media=1 */
|
||||
memset(&nt, 0, sizeof nt);
|
||||
nt.cat = MEOW_NOTIFY_CAT_MEDIA; nt.title = "music"; nt.timeout_ms = -1;
|
||||
uint32_t id2 = meow_notify_send(&nt);
|
||||
check("S2 媒体返本地 id=2", id2 == 2);
|
||||
check("S2 category=media", !strcmp(rec_at(1).n.category, "media"));
|
||||
check("S2 media=1", rec_at(1).n.media == 1);
|
||||
check("S2 expire=0xFFFFFFFF", rec_at(1).n.expire_timeout == 0xFFFFFFFFu);
|
||||
|
||||
/* S3 同类型再发(general 未满 4)→ 新本地 id */
|
||||
memset(&nt, 0, sizeof nt);
|
||||
nt.cat = MEOW_NOTIFY_CAT_GENERAL; nt.title = "t3"; nt.timeout_ms = 0;
|
||||
uint32_t id3 = meow_notify_send(&nt);
|
||||
check("S3 再发返本地 id=3", id3 == 3);
|
||||
check("S3 第2条general nid=103", rec_at(2).nid == 103);
|
||||
|
||||
/* S4 cancel(id1) → DISMISS nid=101 */
|
||||
int rc = meow_notify_cancel(id1);
|
||||
check("S4 cancel 返回 1", rc == 1);
|
||||
check("S4 stub 收到 MSG_NOTIFY_DISMISS", rec_at(3).type == MSG_NOTIFY_DISMISS);
|
||||
check("S4 dismiss 负载 nid=101", rec_at(3).dismiss_id == 101);
|
||||
check("S4 本地表已撤(count=2)", meow_notify_active_count() == 2);
|
||||
|
||||
/* S5 配额满 → 丢最旧 + replaces_id 复用被丢 nid。
|
||||
当前 general 有 id3(1 条);再发 4 条到 4 条(第 4 条触顶后丢最旧)。 */
|
||||
for (int i = 0; i < 4; i++) {
|
||||
memset(&nt, 0, sizeof nt);
|
||||
nt.cat = MEOW_NOTIFY_CAT_GENERAL; nt.title = "quota"; nt.timeout_ms = 0;
|
||||
(void)meow_notify_send(&nt);
|
||||
}
|
||||
/* 现在 general 应仍有 4 条(第5条丢最旧 id3=本地,即被降级替换) */
|
||||
check("S5 配额满后每类型仍=4", meow_notify_count_in_type(MEOW_NOTIFY_CAT_GENERAL) == 4);
|
||||
/* 被丢最旧 = id3(第2条 general,本地 id=3),其 launcher nid=103(rec[2])。
|
||||
触发丢最旧的是 S5 循环第 4 次 send = 第 7 条 NOTIFY(rec[7]),其 replaces_id==103。 */
|
||||
check("S5 丢最旧后 send replaces_id=被丢nid(103)", rec_at(7).n.replaces_id == 103);
|
||||
|
||||
stop_server();
|
||||
pthread_join(th, NULL);
|
||||
|
||||
/* S6 无 launcher 降级 */
|
||||
char dead[108]; snprintf(dead, sizeof dead, "/tmp/meow_notify_intg_dead_%d.sock", (int)getpid());
|
||||
unlink(dead);
|
||||
setenv("MEOW_IPC_SOCK", dead, 1);
|
||||
memset(&nt, 0, sizeof nt);
|
||||
nt.cat = MEOW_NOTIFY_CAT_EMAIL; nt.title = "no-launcher"; nt.timeout_ms = 0;
|
||||
uint32_t id6 = meow_notify_send(&nt);
|
||||
check("S6 无 launcher 仍返回本地 id(>0)", id6 > 0);
|
||||
check("S6 无 launcher active_count 正常", meow_notify_active_count() >= 1);
|
||||
check("S6 cancel 无 launcher 静默不崩", meow_notify_cancel(id6) == 1);
|
||||
|
||||
printf("\n%s (%d failure%s)\n", failures ? "RESULT: FAIL" : "RESULT: PASS",
|
||||
failures, failures == 1 ? "" : "s");
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
* test_notify_ipc.c — 宿主测试:任务 #5 SDK↔IPC 桥(meow_notify_ipc.{h,c})。
|
||||
*
|
||||
* 用本地 AF_UNIX stub socket(回环)验证:
|
||||
* T1 枚举↔字符串映射:CAT_PROGRESS→"progress"、CAT_MEDIA→"media" + media=1;
|
||||
* T2 timeout 映射:-1(ONGOING)→expire=0xFFFFFFFF + ongoing=1;0(NEVER)→expire=0;
|
||||
* >0→该毫秒 + ongoing=0;
|
||||
* T3 回读 MSG_NOTIFY_ACK 拿 launcher 分配的 nid;replaces_id 原样透传;progress 钳位;
|
||||
* T4 无 socket 降级:connect 失败 → meow_notify_ipc_send 返回 0,不崩;
|
||||
* T5 dismiss:meow_notify_ipc_dismiss(nid) 发出 MSG_NOTIFY_DISMISS 帧(负载=nid)。
|
||||
*
|
||||
* 编译(宿主 gcc,不依赖 meow_ui.a 其余 TU):
|
||||
* gcc -o test_notify_ipc test_notify_ipc.c meow_notify_ipc.c \
|
||||
* -I. -I../meow_launcherd -lpthread
|
||||
* 运行:./test_notify_ipc (全部 PASS 退出码 0,任一 FAIL 退出码 1)
|
||||
*/
|
||||
#include "meow_notify_ipc.h"
|
||||
#include "meow_ui.h"
|
||||
#include "meow_ipc.h" /* 帧头 / m_notify_t / m_ack_t / MSG_*(同源校验) */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <pthread.h>
|
||||
|
||||
/* ---- 捕获:stub 服务端解析到的帧内容(mutex 保护;客户端返回后读取) ---- */
|
||||
typedef struct {
|
||||
uint32_t type; /* 收到的帧类型 */
|
||||
m_notify_t n; /* MSG_NOTIFY 负载 */
|
||||
uint32_t dismiss_id; /* MSG_NOTIFY_DISMISS 负载 */
|
||||
uint32_t req_id;
|
||||
uint32_t nid_to_return; /* ACK 中回填的 status */
|
||||
int got; /* 已收到一帧 */
|
||||
} cap_t;
|
||||
static cap_t g_cap;
|
||||
static pthread_mutex_t g_cap_mu = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
static void cap_store(uint32_t type, uint32_t req_id, const uint8_t *pay, uint32_t len,
|
||||
uint32_t nid_to_return)
|
||||
{
|
||||
pthread_mutex_lock(&g_cap_mu);
|
||||
memset(&g_cap, 0, sizeof g_cap);
|
||||
g_cap.type = type;
|
||||
g_cap.req_id = req_id;
|
||||
g_cap.nid_to_return = nid_to_return;
|
||||
if (type == MSG_NOTIFY && len >= sizeof(m_notify_t)) memcpy(&g_cap.n, pay, sizeof(m_notify_t));
|
||||
if (type == MSG_NOTIFY_DISMISS && len >= 4) memcpy(&g_cap.dismiss_id, pay, 4);
|
||||
g_cap.got = 1;
|
||||
pthread_mutex_unlock(&g_cap_mu);
|
||||
}
|
||||
|
||||
/* ---- stub 服务端线程:accept 一个连接 → 读一帧 → 校验由主线程做,这里只收 + 回 ACK ---- */
|
||||
static int g_listen_fd = -1;
|
||||
static void *server_thread(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
int c = accept(g_listen_fd, NULL, NULL);
|
||||
if (c < 0) return NULL;
|
||||
/* 读 16B 头 */
|
||||
uint8_t hdr[MEOW_IPC_HDR_LEN];
|
||||
uint32_t off = 0;
|
||||
while (off < sizeof hdr) {
|
||||
ssize_t r = read(c, hdr + off, sizeof hdr - off);
|
||||
if (r <= 0) { close(c); return NULL; }
|
||||
off += (size_t)r;
|
||||
}
|
||||
uint32_t magic, len;
|
||||
uint16_t ver, type;
|
||||
memcpy(&magic, hdr, 4);
|
||||
memcpy(&ver, hdr + 4, 2);
|
||||
memcpy(&type, hdr + 6, 2);
|
||||
memcpy(&len, hdr + 12, 4);
|
||||
uint32_t req_id; memcpy(&req_id, hdr + 8, 4);
|
||||
if (magic != MEOW_IPC_MAGIC || ver != MEOW_IPC_VERSION) { close(c); return NULL; }
|
||||
if (len > MEOW_IPC_MAX) { close(c); return NULL; }
|
||||
uint8_t pay[MEOW_IPC_MAX];
|
||||
off = 0;
|
||||
while (off < len) {
|
||||
ssize_t r = read(c, pay + off, len - off);
|
||||
if (r <= 0) { close(c); return NULL; }
|
||||
off += (size_t)r;
|
||||
}
|
||||
cap_store(type, req_id, pay, len, (uint32_t)strtoul(getenv("TEST_NID"), NULL, 0));
|
||||
|
||||
/* MSG_NOTIFY → 回 m_ack_t(code=OK, status=nid_to_return);
|
||||
* MSG_NOTIFY_DISMISS → 回 m_ack_t(code=OK, status=0,同 launcher 行为)。 */
|
||||
if (type == MSG_NOTIFY || type == MSG_NOTIFY_DISMISS) {
|
||||
m_ack_t a;
|
||||
memset(&a, 0, sizeof a);
|
||||
a.code = MEOW_OK;
|
||||
a.status = (type == MSG_NOTIFY) ? (uint32_t)strtoul(getenv("TEST_NID"), NULL, 0) : 0;
|
||||
uint8_t rhdr[MEOW_IPC_HDR_LEN];
|
||||
uint32_t rmagic = MEOW_IPC_MAGIC;
|
||||
uint16_t rver = MEOW_IPC_VERSION, rtype = (uint16_t)(type == MSG_NOTIFY ? MSG_NOTIFY_ACK : MSG_ACK);
|
||||
uint32_t rlen = sizeof a;
|
||||
memcpy(rhdr + 0, &rmagic, 4);
|
||||
memcpy(rhdr + 4, &rver, 2);
|
||||
memcpy(rhdr + 6, &rtype, 2);
|
||||
memcpy(rhdr + 8, &req_id, 4);
|
||||
memcpy(rhdr + 12, &rlen, 4);
|
||||
off = 0;
|
||||
while (off < sizeof rhdr) { ssize_t w = write(c, rhdr + off, sizeof rhdr - off); if (w <= 0) break; off += (size_t)w; }
|
||||
off = 0;
|
||||
while (off < rlen) { ssize_t w = write(c, (uint8_t*)&a + off, rlen - off); if (w <= 0) break; off += (size_t)w; }
|
||||
}
|
||||
close(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int failures = 0;
|
||||
|
||||
static void check(const char *name, int cond)
|
||||
{
|
||||
printf("%s %s\n", cond ? "PASS" : "FAIL", name);
|
||||
if (!cond) failures++;
|
||||
}
|
||||
|
||||
/* 建 listening socket,返回 fd;路径写入 sock_path。 */
|
||||
static int arb_listen(char *sock_path, size_t slen)
|
||||
{
|
||||
static int count = 0;
|
||||
snprintf(sock_path, slen, "/tmp/meow_notify_ipc_%d_%d.sock", (int)getpid(), count++);
|
||||
unlink(sock_path);
|
||||
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (fd < 0) { perror("socket"); return -1; }
|
||||
struct sockaddr_un a;
|
||||
memset(&a, 0, sizeof a);
|
||||
a.sun_family = AF_UNIX;
|
||||
strncpy(a.sun_path, sock_path, sizeof a.sun_path - 1);
|
||||
if (bind(fd, (struct sockaddr *)&a, sizeof a) < 0) { perror("bind"); close(fd); return -1; }
|
||||
if (listen(fd, 1) < 0) { perror("listen"); close(fd); return -1; }
|
||||
return fd;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
setenv("MEOW_LOG_APP", "testapp", 1); /* 固定 app_id,供 stub 校验 */
|
||||
|
||||
/* ---------- T1/T2/T3:映射 + ACK 回 nid ---------- */
|
||||
{
|
||||
char sock[108];
|
||||
g_listen_fd = arb_listen(sock, sizeof sock);
|
||||
if (g_listen_fd < 0) { fprintf(stderr, "cannot create listener\n"); return 2; }
|
||||
setenv("MEOW_IPC_SOCK", sock, 1);
|
||||
setenv("TEST_NID", "0x1234", 1);
|
||||
pthread_t th;
|
||||
pthread_create(&th, NULL, server_thread, NULL);
|
||||
|
||||
meow_notify nt;
|
||||
memset(&nt, 0, sizeof nt);
|
||||
nt.cat = MEOW_NOTIFY_CAT_PROGRESS;
|
||||
nt.urgency = MEOW_NOTIFY_URG_NORMAL;
|
||||
nt.title = "下载中";
|
||||
nt.body = "70%";
|
||||
nt.timeout_ms = 5000; /* >0 → expire=5000, ongoing=0 */
|
||||
nt.progress = 150; /* 钳位 → 100 */
|
||||
uint32_t nid = meow_notify_ipc_send(&nt, 0);
|
||||
pthread_join(th, NULL);
|
||||
close(g_listen_fd);
|
||||
|
||||
check("T3 send 返回 launcher nid(0x1234)", nid == 0x1234);
|
||||
pthread_mutex_lock(&g_cap_mu);
|
||||
int got = g_cap.got;
|
||||
const m_notify_t *cn = &g_cap.n;
|
||||
pthread_mutex_unlock(&g_cap_mu);
|
||||
check("T3 stub 收到 MSG_NOTIFY 帧", got && g_cap.type == MSG_NOTIFY);
|
||||
check("T1 CAT_PROGRESS→category=progress", !strcmp(cn->category, "progress"));
|
||||
check("T2 timeout>0→expire_timeout=5000", cn->expire_timeout == 5000);
|
||||
check("T2 timeout>0→ongoing=0", cn->ongoing == 0);
|
||||
check("T1 media=0(非媒体)", cn->media == 0);
|
||||
check("T1 progress 钳位 150→100", cn->progress == 100);
|
||||
check("T2 replaces_id 透传=0", cn->replaces_id == 0);
|
||||
check("T1 app_id=testapp", !strcmp(cn->app_id, "testapp"));
|
||||
check("T1 summary=下载中", !strcmp(cn->summary, "下载中"));
|
||||
}
|
||||
|
||||
/* ---------- T1/T2:媒体类型 + ONGOING(-1) ---------- */
|
||||
{
|
||||
char sock[108];
|
||||
g_listen_fd = arb_listen(sock, sizeof sock);
|
||||
setenv("MEOW_IPC_SOCK", sock, 1);
|
||||
setenv("TEST_NID", "0x9999", 1);
|
||||
pthread_t th;
|
||||
pthread_create(&th, NULL, server_thread, NULL);
|
||||
|
||||
meow_notify nt;
|
||||
memset(&nt, 0, sizeof nt);
|
||||
nt.cat = MEOW_NOTIFY_CAT_MEDIA;
|
||||
nt.urgency = MEOW_NOTIFY_URG_CRITICAL;
|
||||
nt.title = "Now Playing";
|
||||
nt.timeout_ms = MEOW_NOTIFY_TIMEOUT_ONGOING; /* -1 */
|
||||
uint32_t nid = meow_notify_ipc_send(&nt, 0);
|
||||
pthread_join(th, NULL);
|
||||
close(g_listen_fd);
|
||||
|
||||
check("T3 send(media/ongoing) 返回 nid(0x9999)", nid == 0x9999);
|
||||
check("T1 CAT_MEDIA→category=media", !strcmp(g_cap.n.category, "media"));
|
||||
check("T1 CAT_MEDIA→media=1", g_cap.n.media == 1);
|
||||
check("T2 ONGOING(-1)→expire=0xFFFFFFFF", g_cap.n.expire_timeout == 0xFFFFFFFFu);
|
||||
check("T2 ONGOING(-1)→ongoing=1", g_cap.n.ongoing == 1);
|
||||
}
|
||||
|
||||
/* ---------- T2:NEVER(0) 超时 ---------- */
|
||||
{
|
||||
char sock[108];
|
||||
g_listen_fd = arb_listen(sock, sizeof sock);
|
||||
setenv("MEOW_IPC_SOCK", sock, 1);
|
||||
setenv("TEST_NID", "0x1", 1);
|
||||
pthread_t th;
|
||||
pthread_create(&th, NULL, server_thread, NULL);
|
||||
meow_notify nt;
|
||||
memset(&nt, 0, sizeof nt);
|
||||
nt.cat = MEOW_NOTIFY_CAT_GENERAL;
|
||||
nt.title = "x";
|
||||
nt.timeout_ms = MEOW_NOTIFY_TIMEOUT_NEVER; /* 0 */
|
||||
(void)meow_notify_ipc_send(&nt, 0);
|
||||
pthread_join(th, NULL);
|
||||
close(g_listen_fd);
|
||||
check("T2 NEVER(0)→expire=0 & ongoing=0",
|
||||
g_cap.n.expire_timeout == 0 && g_cap.n.ongoing == 0);
|
||||
check("T1 CAT_GENERAL→category=general", !strcmp(g_cap.n.category, "general"));
|
||||
}
|
||||
|
||||
/* ---------- T3:replaces_id 透传 ---------- */
|
||||
{
|
||||
char sock[108];
|
||||
g_listen_fd = arb_listen(sock, sizeof sock);
|
||||
setenv("MEOW_IPC_SOCK", sock, 1);
|
||||
setenv("TEST_NID", "0xBEEF", 1);
|
||||
pthread_t th;
|
||||
pthread_create(&th, NULL, server_thread, NULL);
|
||||
meow_notify nt;
|
||||
memset(&nt, 0, sizeof nt);
|
||||
nt.cat = MEOW_NOTIFY_CAT_MESSAGE;
|
||||
nt.title = "repl";
|
||||
nt.timeout_ms = 0;
|
||||
(void)meow_notify_ipc_send(&nt, 0x42u);
|
||||
pthread_join(th, NULL);
|
||||
close(g_listen_fd);
|
||||
check("T3 replaces_id 透传=0x42", g_cap.n.replaces_id == 0x42u);
|
||||
}
|
||||
|
||||
/* ---------- T4:无 socket 降级不崩 ---------- */
|
||||
{
|
||||
/* 指向不存在路径 */
|
||||
char dead[108];
|
||||
snprintf(dead, sizeof dead, "/tmp/meow_notify_ipc_dead_%d.sock", (int)getpid());
|
||||
unlink(dead);
|
||||
setenv("MEOW_IPC_SOCK", dead, 1);
|
||||
meow_notify nt;
|
||||
memset(&nt, 0, sizeof nt);
|
||||
nt.cat = MEOW_NOTIFY_CAT_EMAIL;
|
||||
nt.title = "no-socket";
|
||||
nt.timeout_ms = 0;
|
||||
uint32_t nid = meow_notify_ipc_send(&nt, 0);
|
||||
check("T4 无 socket→返回 0(降级不崩)", nid == 0);
|
||||
meow_notify_ipc_dismiss(0x1234); /* 无 socket 静默,不崩 */
|
||||
check("T4 dismiss 无 socket 静默不崩", 1);
|
||||
}
|
||||
|
||||
/* ---------- T5:dismiss 发 MSG_NOTIFY_DISMISS ---------- */
|
||||
{
|
||||
char sock[108];
|
||||
g_listen_fd = arb_listen(sock, sizeof sock);
|
||||
setenv("MEOW_IPC_SOCK", sock, 1);
|
||||
pthread_t th;
|
||||
pthread_create(&th, NULL, server_thread, NULL);
|
||||
meow_notify_ipc_dismiss(0xABCDu);
|
||||
pthread_join(th, NULL);
|
||||
close(g_listen_fd);
|
||||
check("T5 stub 收到 MSG_NOTIFY_DISMISS 帧", g_cap.got && g_cap.type == MSG_NOTIFY_DISMISS);
|
||||
check("T5 dismiss 负载=nid=0xABCD", g_cap.dismiss_id == 0xABCDu);
|
||||
}
|
||||
|
||||
printf("\n%s (%d failure%s)\n", failures ? "RESULT: FAIL" : "RESULT: PASS",
|
||||
failures, failures == 1 ? "" : "s");
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* test_notify_post.c — 宿主交叉验证:SDK send 的 IPC 帧 → launcher 真·中央存储。
|
||||
*
|
||||
* 把 SDK 的 meow_notify_ipc.c(映射+传输)与 launcher 的 meow_notif.c(真·中央存储,
|
||||
* meow_notif_post/dismiss)接到一起,stub 掉 meow_settings 后端;服务端复刻
|
||||
* meow_launcherd.c:6427-6440 的 m_notify_t→meow_notif_t 映射 + MSG_NOTIFY_ACK 应答。
|
||||
*
|
||||
* 验证:SDK meow_notify_ipc_send() 产出的帧被 meow_notif_post() 真实落库并回分配 nid>0;
|
||||
* 随后 meow_notify_ipc_dismiss() 经 meow_notif_dismiss() 撤下(count 归 0)。
|
||||
*
|
||||
* 编译(宿主 gcc):
|
||||
* gcc -o test_notify_post test_notify_post.c meow_notify_ipc.c \
|
||||
* ../meow_launcherd/meow_notif.c -I. -I../meow_launcherd -lpthread
|
||||
*/
|
||||
#include "meow_notify_ipc.h"
|
||||
#include "meow_ui.h"
|
||||
#include "meow_ipc.h"
|
||||
#include "meow_notif.h" /* launcher 真·中央存储 */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <pthread.h>
|
||||
|
||||
/* ---- stub:settings 后端(键缺省 → meow_notif.c 用默认配额) ---- */
|
||||
int meow_settings_get_int(const char *key, int *out) { (void)key; (void)out; return MEOW_ERR_NOENT; }
|
||||
|
||||
/* ---- 复刻 launcher 的 m_notify_t → meow_notif_t 映射(meow_launcherd.c:6427-6440) ----
|
||||
* 注:与 launcher 同款 snprintf(dest 较 src 小会有 format-truncation 提示),launcher 自身
|
||||
* 以其编译 flags 构建无警告;此处仅为宿主测试,用 pragma 压掉以免 -Werror 干扰。 */
|
||||
#if defined(__GNUC__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wformat-truncation"
|
||||
#endif
|
||||
static void launcher_map(const m_notify_t *mn, meow_notif_t *n)
|
||||
{
|
||||
memset(n, 0, sizeof *n);
|
||||
n->id = mn->replaces_id; /* 0=>新建;>0=>替换(不存在则新建) */
|
||||
snprintf(n->app_id, sizeof n->app_id, "%s", mn->app_id);
|
||||
snprintf(n->category, sizeof n->category, "%s", mn->category);
|
||||
snprintf(n->summary, sizeof n->summary, "%s", mn->summary);
|
||||
snprintf(n->body, sizeof n->body, "%s", mn->body);
|
||||
snprintf(n->app_icon, sizeof n->app_icon, "%s", mn->app_icon);
|
||||
snprintf(n->target_activity, sizeof n->target_activity, "%s", mn->target_activity);
|
||||
n->expire_timeout = mn->expire_timeout;
|
||||
n->progress = mn->progress;
|
||||
n->ongoing = mn->ongoing;
|
||||
n->media = mn->media;
|
||||
}
|
||||
#if defined(__GNUC__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
static int g_listen_fd = -1;
|
||||
static int g_stop = 0;
|
||||
static uint32_t g_posted_nid = 0;
|
||||
|
||||
static void reply_ack(int c, uint16_t type, uint32_t req_id, uint32_t status)
|
||||
{
|
||||
m_ack_t a; memset(&a, 0, sizeof a); a.code = MEOW_OK; a.status = status;
|
||||
uint8_t rhdr[MEOW_IPC_HDR_LEN];
|
||||
uint32_t mag = MEOW_IPC_MAGIC; uint16_t ver = MEOW_IPC_VERSION, t = type; uint32_t rl = sizeof a;
|
||||
memcpy(rhdr+0,&mag,4); memcpy(rhdr+4,&ver,2); memcpy(rhdr+6,&t,2); memcpy(rhdr+8,&req_id,4); memcpy(rhdr+12,&rl,4);
|
||||
size_t o = 0; while (o < sizeof rhdr) { ssize_t w = write(c, rhdr+o, sizeof rhdr-o); if (w<=0) break; o += (size_t)w; }
|
||||
o = 0; while (o < rl) { ssize_t w = write(c, (uint8_t*)&a+o, rl-o); if (w<=0) break; o += (size_t)w; }
|
||||
}
|
||||
|
||||
static void *server(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
meow_notif_init();
|
||||
while (!g_stop) {
|
||||
int c = accept(g_listen_fd, NULL, NULL);
|
||||
if (c < 0) { if (errno == EAGAIN && g_stop) break; if (errno == EBADF) break; usleep(1000); continue; }
|
||||
uint8_t hdr[MEOW_IPC_HDR_LEN]; size_t o = 0;
|
||||
while (o < sizeof hdr) { ssize_t r = read(c, hdr+o, sizeof hdr-o); if (r<=0) break; o += (size_t)r; }
|
||||
if (o < sizeof hdr) { close(c); continue; }
|
||||
uint32_t type, len, req_id; memcpy(&type, hdr+6, 2); memcpy(&req_id, hdr+8, 4); memcpy(&len, hdr+12, 4);
|
||||
if (len > MEOW_IPC_MAX) { close(c); continue; }
|
||||
uint8_t pay[MEOW_IPC_MAX]; o = 0;
|
||||
while (o < len) { ssize_t r = read(c, pay+o, len-o); if (r<=0) break; o += (size_t)r; }
|
||||
|
||||
if (type == MSG_NOTIFY && len >= sizeof(m_notify_t)) {
|
||||
m_notify_t mn; memcpy(&mn, pay, sizeof mn);
|
||||
meow_notif_t n; launcher_map(&mn, &n);
|
||||
uint32_t nid = 0;
|
||||
int rc = meow_notif_post(&n, &nid);
|
||||
if (rc == NOTIF_OK) g_posted_nid = nid;
|
||||
reply_ack(c, MSG_NOTIFY_ACK, req_id, (rc == NOTIF_OK) ? nid : (uint32_t)rc);
|
||||
fprintf(stderr, "[post-srv] post rc=%d nid=%u cat=%s\n", rc, nid, mn.category);
|
||||
} else if (type == MSG_NOTIFY_DISMISS && len >= 4) {
|
||||
uint32_t id; memcpy(&id, pay, 4);
|
||||
int rc = meow_notif_dismiss(id);
|
||||
reply_ack(c, MSG_ACK, req_id, 0);
|
||||
fprintf(stderr, "[post-srv] dismiss id=%u rc=%d\n", id, rc);
|
||||
}
|
||||
close(c);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int make_listen(char *path, size_t n)
|
||||
{
|
||||
snprintf(path, n, "/tmp/meow_notify_post_%d.sock", (int)getpid());
|
||||
unlink(path);
|
||||
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
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 (bind(fd, (struct sockaddr*)&a, sizeof a) < 0 || listen(fd, 4) < 0) { close(fd); return -1; }
|
||||
fcntl(fd, F_SETFL, O_NONBLOCK);
|
||||
g_listen_fd = fd;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int failures = 0;
|
||||
static void check(const char *name, int cond) { printf("%s %s\n", cond ? "PASS" : "FAIL", name); if (!cond) failures++; }
|
||||
|
||||
int main(void)
|
||||
{
|
||||
setenv("MEOW_LOG_APP", "intgapp", 1);
|
||||
char sock[108];
|
||||
if (make_listen(sock, sizeof sock) != 0) { fprintf(stderr, "cannot listen\n"); return 2; }
|
||||
setenv("MEOW_IPC_SOCK", sock, 1);
|
||||
pthread_t th; pthread_create(&th, NULL, server, NULL);
|
||||
|
||||
/* SDK send → launcher 真存储。media + ONGOING 走全部映射分支。 */
|
||||
meow_notify nt; memset(&nt, 0, sizeof nt);
|
||||
nt.cat = MEOW_NOTIFY_CAT_MEDIA; nt.urgency = MEOW_NOTIFY_URG_NORMAL;
|
||||
nt.title = "正在播放"; nt.timeout_ms = -1; nt.progress = 40;
|
||||
uint32_t nid = meow_notify_ipc_send(&nt, 0);
|
||||
check("send 经 launcher 真存储回 nid>0", nid != 0);
|
||||
check("send 拿到 launcher 分配的 nid(与 post 一致)", g_posted_nid == nid);
|
||||
check("launcher 存储 count(media app)=1", meow_notif_count("intgapp") == 1);
|
||||
|
||||
/* SDK dismiss → launcher 撤下 */
|
||||
meow_notify_ipc_dismiss(nid);
|
||||
check("dismiss 后 launcher 存储 count=0", meow_notif_count("intgapp") == 0);
|
||||
|
||||
g_stop = 1; close(g_listen_fd);
|
||||
pthread_join(th, NULL);
|
||||
meow_notif_shutdown();
|
||||
|
||||
printf("\n%s (%d failure%s)\n", failures ? "RESULT: FAIL" : "RESULT: PASS", failures, failures == 1 ? "" : "s");
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
Reference in new issue
Block a user