diff --git a/build_sdk.sh b/build_sdk.sh index b7440e4..2150cfc 100755 --- a/build_sdk.sh +++ b/build_sdk.sh @@ -10,7 +10,7 @@ AR="${AR:-/usr/bin/ar}" # 系统 GNU ar 即可归档 mipsel ELF 对象(架 rm -f meow_ui.a *.o # -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 meow_ui_activity meow_ui_global meow_ui_dialog meow_ui_config meow_ui_draw meow_ui_view stb_image" +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 meow_ui_activity meow_ui_global meow_ui_dialog meow_ui_config meow_ui_draw meow_ui_view meow_ui_nav meow_ui_media stb_image" CFLAGS="-Os -static -I$ROOT/patch_v2/meow_launcherd" for f in $OBJS; do echo "== cc $f ==" diff --git a/meow_notify_ipc.c b/meow_notify_ipc.c index 6486fed..5445b6a 100644 --- a/meow_notify_ipc.c +++ b/meow_notify_ipc.c @@ -280,3 +280,55 @@ void meow_notify_ipc_update_progress(uint32_t nid, int progress) } close(fd); } + +/* ---- 通用一次性帧(meow_ui_nav / notif_open 复用本传输层) ---- */ +int meow_notify_ipc_send_raw(uint16_t type, const void *payload, uint32_t len, int want_ack) +{ + int fd = conn_path(ipc_sock_path()); + if (fd < 0) return -1; + set_ack_timeout(fd); + if (!send_frame(fd, type, 0, payload, len)) { close(fd); return -1; } + if (want_ack) { + /* 阻塞读一帧应答头(best-effort:读到即确认 launcher 已处理;内容不关心)。 + * 超时(SO_RCVTIMEO)/断开 → read_all 返回 0,视为已发出(不崩)。 */ + uint8_t rhdr[MEOW_IPC_HDR_LEN]; + if (read_all(fd, rhdr, sizeof rhdr)) { + /* 读完应答负载(若声明),保持连接语义一致;不校验 magic(best-effort) */ + uint32_t rlen; + memcpy(&rlen, rhdr + 12, 4); + if (rlen > 0 && rlen <= MEOW_IPC_MAX) { + uint8_t pay[256]; + size_t to = rlen < sizeof pay ? rlen : sizeof pay; + (void)read_all(fd, pay, to); + } + } + } + close(fd); + return 0; +} + +void meow_notify_ipc_notif_open(const char *app_id, uint32_t event_seq, + uint16_t origin_x, uint16_t origin_y, + uint16_t hotzone, uint8_t flags) +{ + m_notif_open_t o; + memset(&o, 0, sizeof o); + snprintf(o.app_id, sizeof o.app_id, "%s", + (app_id && app_id[0]) ? app_id : meow_notify_ipc_app_id()); + o.event_seq = event_seq; + o.origin_x = origin_x; + o.origin_y = origin_y; + o.hotzone = hotzone; + o.flags = flags; + (void)meow_notify_ipc_send_raw(MSG_NOTIF_OPEN, &o, (uint32_t)sizeof o, 1); +} + +void meow_notify_ipc_notif_close(const char *app_id, uint32_t reason) +{ + m_notif_close_t o; + memset(&o, 0, sizeof o); + snprintf(o.app_id, sizeof o.app_id, "%s", + (app_id && app_id[0]) ? app_id : meow_notify_ipc_app_id()); + o.reason = reason; + (void)meow_notify_ipc_send_raw(MSG_NOTIF_CLOSE, &o, (uint32_t)sizeof o, 1); +} diff --git a/meow_notify_ipc.h b/meow_notify_ipc.h index f02a5a0..14df264 100644 --- a/meow_notify_ipc.h +++ b/meow_notify_ipc.h @@ -43,6 +43,20 @@ void meow_notify_ipc_dismiss(uint32_t nid); * 不落盘)。best-effort:写失败/无 socket 静默。 */ void meow_notify_ipc_update_progress(uint32_t nid, int progress); +/* 通用一次性请求:发 type+payload 帧,want_ack 时阻塞读一帧应答(忽略其内容,仅确保 + * launcher 已处理完再关连接,防「写后即关」丢帧竞态)。返回 0=已发出(want_ack 含读到应答); + * -1=无 socket/写失败/读失败。best-effort,供 SDK 内部 nav / notif_open 复用。 */ +int meow_notify_ipc_send_raw(uint16_t type, const void *payload, uint32_t len, int want_ack); + +/* 一次性发送 MSG_NOTIF_OPEN(App 前台顶部下滑 → 请求 launcher 展开通知中心叠加会话, + * FRAMEWORK_DESIGN §10.5)。App 只检测+上报、不渲染系统 UI。best-effort:失败静默。 */ +void meow_notify_ipc_notif_open(const char *app_id, uint32_t event_seq, + uint16_t origin_x, uint16_t origin_y, + uint16_t hotzone, uint8_t flags); + +/* 一次性发送 MSG_NOTIF_CLOSE(App 告知结束/退出 → launcher 收尾兜底,可选用途)。best-effort。 */ +void meow_notify_ipc_notif_close(const char *app_id, uint32_t reason); + #ifdef __cplusplus } #endif diff --git a/meow_ui.h b/meow_ui.h index 0a21284..63a82c3 100644 --- a/meow_ui.h +++ b/meow_ui.h @@ -218,6 +218,7 @@ int meow_ui_long_press_ms(void); /* 当前阈值(默认 void meow_ui_activity_push(int id); void meow_ui_activity_pop(void); int meow_ui_activity_active(void); /* 当前 active activity id(栈空=0) */ +int meow_ui_activity_depth(void); /* 当前栈深(push/pop 页数;供 M3 导航上报用) */ /* ② App 跨 Activity 控件(SDK 托管,骨架)。SDK 维护全局控件列表: * - 渲染:每帧(有脏区需上屏时,meow_ui_gfx present())在 active activity 内容之上、 diff --git a/meow_ui_activity.c b/meow_ui_activity.c index 205d215..84f29ef 100644 --- a/meow_ui_activity.c +++ b/meow_ui_activity.c @@ -31,3 +31,8 @@ int meow_ui_activity_active(void) { return g_act_n > 0 ? g_act[g_act_n - 1] : 0; } + +int meow_ui_activity_depth(void) +{ + return g_act_n; +} diff --git a/meow_ui_input.c b/meow_ui_input.c index 1629eb7..a931b65 100644 --- a/meow_ui_input.c +++ b/meow_ui_input.c @@ -436,8 +436,9 @@ void meow_ui_run(const meow_ui_events *ev) } else if (e.type == MEOW_EV_NOTIF_PULL) { if (in_startup) continue; /* 启动窗口顶区误触残留:丢弃,防进入即开通知中心 */ /* 系统级「下拉展开通知中心」:起始于顶部热区的下滑为系统保留手势,不下发 - * APP on_swipe;路由到系统钩子(当前为记录占位,通知中心 UI 为后续里程碑)。 */ - meow_ui_sys_notif_pull(); + * APP on_swipe;路由到系统钩子(App 进程只检测+经 IPC 上报 MSG_NOTIF_OPEN, + * 通知中心由 launcher 渲染,见 FRAMEWORK_DESIGN §10.5)。 */ + meow_ui_sys_notif_pull(e.a); /* e.a = 手势按下原点 Y */ } else if (e.type == MEOW_EV_TOUCH_DOWN || e.type == MEOW_EV_TOUCH_MOVE) { /* M2.71-bug2/6:拖动过程(跟手)。IME 弹出时触摸优先喂键盘(键盘覆盖层拦截, * on_touch_* 丢弃,避免按到键盘却在背后控件上锁拖拽);启动窗口也吞(防残留点按 diff --git a/meow_ui_media.c b/meow_ui_media.c new file mode 100644 index 0000000..6063642 --- /dev/null +++ b/meow_ui_media.c @@ -0,0 +1,253 @@ +/* meow_ui_media.c — App 侧媒体会话封装(SDK 公共件;见 meow_ui_media.h)。 + * + * 传输:AF_UNIX SOCK_STREAM 到 MEOW_IPC_SOCK(缺省 /usr/data/meow_ipc.sock)。后台线程持有 + * 持久连接:HELLO(app_id, MF_PLAYBACK) 注册为媒体源 + poll 读 launcher 下发 MSG_PLAYCTL(经 + * 该连接 fd 回发,见 launcher meow_media.c send_playctl)。meow_ui_media_meta() 在调用线程 + * 直接经同一连接发 MSG_META(全双工:写 META、读 PLAYCTL 互不干扰)。 + * + * 连接生命周期(best-effort):懒连(首次 meta / 注册回调时 connect+HELLO+ACK); + * 断线(读失败/写失败)→ close+置 -1,下次 meta 重连;launcher 不在 → connect 失败 → 静默。 + * 线程安全:g_fd 读写经互斥;后台线程只在 poll/读失败时改 fd,meta 线程只在 connect 时改 fd。 + */ +#include "meow_ui_media.h" +#include "meow_ipc.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER; +static char g_app[MEOW_APP_ID_MAX] = ""; +static int g_app_set = 0; +static int g_fd = -1; +static pthread_t g_thr; +static int g_thr_on = 0; +static volatile int g_quit = 0; +static meow_media_playctl_fn g_cb = NULL; + +/* 后台线程主体(定义在文件后段;on_playctl 懒起时引用)。 */ +static void *media_thread_main(void *arg); + +static const char *sock_path(void) +{ + const char *e = getenv("MEOW_IPC_SOCK"); + return (e && e[0]) ? e : MEOW_IPC_PATH; +} + +static int connect_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; +} + +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 -1; + if (len && payload && !write_all(fd, payload, len)) return -1; + return 0; +} + +/* 读一帧头+负载;返回负载长(≥0),-1=断/坏帧。 */ +static int read_frame(int fd, uint16_t *type, uint8_t *rbuf, uint32_t rcap) +{ + uint8_t hdr[MEOW_IPC_HDR_LEN]; + if (!read_all(fd, hdr, sizeof hdr)) return -1; + uint32_t magic, len; uint16_t ver; + memcpy(&magic, hdr, 4); memcpy(&ver, hdr + 4, 2); memcpy(type, hdr + 6, 2); + memcpy(&len, hdr + 12, 4); + if (magic != MEOW_IPC_MAGIC || ver != MEOW_IPC_VERSION) return -1; + if (len > rcap) len = rcap; + if (len > 0 && !read_all(fd, rbuf, len)) return -1; + return (int)len; +} + +static void media_app_id(void) +{ + if (g_app_set) return; + const char *s = getenv("MEOW_LOG_APP"); + snprintf(g_app, sizeof g_app, "%s", (s && s[0]) ? s : "app"); + g_app_set = 1; +} + +/* (调用方已持 mu)连接 + HELLO(MF_PLAYBACK) + 读 ACK。成功存 g_fd 返回 fd;失败 -1。 */ +static int conn_locked(void) +{ + if (g_fd >= 0) return g_fd; + int fd = connect_path(sock_path()); + if (fd < 0) return -1; + media_app_id(); + m_hello_t h; + memset(&h, 0, sizeof h); + snprintf(h.app_id, sizeof h.app_id, "%s", g_app); + h.version = 1; + h.flags = MF_PLAYBACK; + if (send_frame(fd, MSG_HELLO, 1, &h, sizeof h) != 0) { close(fd); return -1; } + uint8_t rbuf[256]; + uint16_t rt; + if (read_frame(fd, &rt, rbuf, sizeof rbuf) < 0 || + (rt != MSG_ACK && rt != MSG_ERR)) { close(fd); return -1; } + g_fd = fd; + return fd; +} + +/* (调用方已持 mu)标记断线并关闭。 */ +static void mark_dead_locked(void) +{ + if (g_fd >= 0) { close(g_fd); g_fd = -1; } +} + +void meow_ui_media_init(const char *app_id) +{ + pthread_mutex_lock(&g_mu); + if (app_id && app_id[0]) { + snprintf(g_app, sizeof g_app, "%s", app_id); + g_app_set = 1; + } else if (!g_app_set) { + const char *s = getenv("MEOW_LOG_APP"); + snprintf(g_app, sizeof g_app, "%s", (s && s[0]) ? s : "app"); + g_app_set = 1; + } + pthread_mutex_unlock(&g_mu); +} + +void meow_ui_media_on_playctl(meow_media_playctl_fn cb) +{ + pthread_mutex_lock(&g_mu); + g_cb = cb; + int need_start = !g_thr_on; + if (need_start) { g_quit = 0; g_thr_on = 1; } + pthread_mutex_unlock(&g_mu); + if (need_start) pthread_create(&g_thr, NULL, media_thread_main, NULL); + /* detach:App 退出经 meow_ui_media_stop 设 g_quit 后线程自身退出,无需 join */ + if (need_start) pthread_detach(g_thr); +} + +void meow_ui_media_meta(uint32_t state, uint32_t pos_ms, uint32_t dur_ms, + const char *title, const char *artist, const char *album, + const char *artwork, const char *lyric) +{ + m_meta_t mm; + memset(&mm, 0, sizeof mm); + pthread_mutex_lock(&g_mu); + media_app_id(); + snprintf(mm.app_id, sizeof mm.app_id, "%s", g_app); + mm.state = state; + mm.pos_ms = pos_ms; + mm.dur_ms = dur_ms; + if (title) snprintf(mm.title, sizeof mm.title, "%s", title); + if (artist) snprintf(mm.artist, sizeof mm.artist, "%s", artist); + if (album) snprintf(mm.album, sizeof mm.album, "%s", album); + if (artwork && artwork[0]) { snprintf(mm.artwork, sizeof mm.artwork, "%s", artwork); mm.flags |= MF_ART | MF_COVER; } + if (lyric && lyric[0]) { snprintf(mm.lyric, sizeof mm.lyric, "%s", lyric); mm.flags |= MF_LYRIC; } + + int fd = conn_locked(); + if (fd >= 0) { + if (send_frame(fd, MSG_META, (uint32_t)((uint64_t)pos_ms & 0x7FFFFFFF), &mm, sizeof mm) != 0) { + mark_dead_locked(); /* 断线:下次 meta 重连 */ + fd = conn_locked(); + if (fd >= 0) (void)send_frame(fd, MSG_META, (uint32_t)((uint64_t)pos_ms & 0x7FFFFFFF), &mm, sizeof mm); + } + } + pthread_mutex_unlock(&g_mu); +} + +void meow_ui_media_stop(void) +{ + pthread_mutex_lock(&g_mu); + g_quit = 1; + mark_dead_locked(); + g_thr_on = 0; + pthread_mutex_unlock(&g_mu); +} + +/* 后台线程主体:懒连 + poll 读 launcher 下发 MSG_PLAYCTL → 调 cb。 */ +static void *media_thread_main(void *arg) +{ + (void)arg; + while (!g_quit) { + pthread_mutex_lock(&g_mu); + int fd = g_fd; + if (fd < 0) fd = conn_locked(); /* 懒连/重连(best-effort;失败则回到循环再试) */ + pthread_mutex_unlock(&g_mu); + if (fd < 0) { usleep(500000); continue; } + + struct pollfd p; + p.fd = fd; p.events = POLLIN; p.revents = 0; + int pr = poll(&p, 1, 200); + if (pr < 0 && errno != EINTR) { + pthread_mutex_lock(&g_mu); mark_dead_locked(); pthread_mutex_unlock(&g_mu); + continue; + } + if (pr > 0 && (p.revents & (POLLHUP | POLLERR | POLLNVAL))) { + pthread_mutex_lock(&g_mu); mark_dead_locked(); pthread_mutex_unlock(&g_mu); + continue; + } + if (pr > 0 && (p.revents & POLLIN)) { + uint8_t rbuf[MEOW_IPC_MAX]; + uint16_t type; + int n = read_frame(fd, &type, rbuf, sizeof rbuf); + if (n < 0) { + pthread_mutex_lock(&g_mu); mark_dead_locked(); pthread_mutex_unlock(&g_mu); + continue; + } + if (type == MSG_PLAYCTL && n >= (int)sizeof(m_playctl_t)) { + m_playctl_t c; + memcpy(&c, rbuf, sizeof c); + /* g_cb 经互斥读快照后调用(App 回调可能在 UI 线程,简化为直接调) */ + pthread_mutex_lock(&g_mu); + meow_media_playctl_fn cb = g_cb; + pthread_mutex_unlock(&g_mu); + if (cb) cb((int)c.cmd, (int64_t)c.seek_ms); + } + } + } + pthread_mutex_lock(&g_mu); mark_dead_locked(); pthread_mutex_unlock(&g_mu); + return NULL; +} diff --git a/meow_ui_media.h b/meow_ui_media.h new file mode 100644 index 0000000..8bdcfae --- /dev/null +++ b/meow_ui_media.h @@ -0,0 +1,45 @@ +/* meow_ui_media.h — App 侧媒体会话封装(SDK 公共件;FRAMEWORK_DESIGN 附录 B / D1)。 + * + * 让框架/第三方播放类 App 以 SDK 标准上报实时元数据(MSG_META)并接收控制(MSG_PLAYCTL)。 + * 声明 MF_PLAYBACK 的 App 首次上报 → launcher 注册为媒体源(最近活跃=当前源);通知中心 + * 媒体卡片 / 主界面 Now Playing 页据此显示与控制。 + * + * 连接管理(best-effort):懒连(首次 meta/注册回调时 connect)+ 断线重连(下次上报时重试); + * launcher 不在 → 优雅静默降级,绝不崩。帧格式/负载复用 launcher 共享 meow_ipc.h(同一份)。 + * + * 命名:launcher 侧已占用 meow_media_*(meow_media.c),SDK 侧统一 meow_ui_media_* 前缀, + * 避免两态同符号在 launcher(链 meow_ui.a + meow_media.c)出现重复定义。 + */ +#ifndef MEOW_UI_MEDIA_H +#define MEOW_UI_MEDIA_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* 播放控制回调:cmd 1=播放 2=暂停 3=上一首 4=下一首 5=seek(seek_ms 为毫秒)。 */ +typedef void (*meow_media_playctl_fn)(int cmd, int64_t seek_ms); + +/* 指定 app_id(NULL=env MEOW_LOG_APP,缺省 "app")。幂等,首次调用生效。 */ +void meow_ui_media_init(const char *app_id); + +/* 注册播放控制回调并启动后台接收线程(懒起;重复调用幂等)。 + * 线程:连接(HELLO+MF_PLAYBACK 注册为媒体源)→ poll 读 launcher 下发 MSG_PLAYCTL → 调 cb。 */ +void meow_ui_media_on_playctl(meow_media_playctl_fn cb); + +/* 上报实时元数据/进度/状态(state:0=停止 1=播放 2=暂停 3=缓冲)。 + * artwork/lyric 非空 → 自动置 MF_ART/MF_COVER/MF_LYRIC 标志;封面/歌词路径或 MeowOS 槽位。 + * best-effort:无 socket/launcher 不在 → 静默降级(打日志不崩)。 */ +void meow_ui_media_meta(uint32_t state, uint32_t pos_ms, uint32_t dur_ms, + const char *title, const char *artist, const char *album, + const char *artwork, const char *lyric); + +/* 停止后台接收线程并断开连接(App 退出时调;幂等)。 */ +void meow_ui_media_stop(void); + +#ifdef __cplusplus +} +#endif +#endif /* MEOW_UI_MEDIA_H */ diff --git a/meow_ui_nav.c b/meow_ui_nav.c new file mode 100644 index 0000000..69897b4 --- /dev/null +++ b/meow_ui_nav.c @@ -0,0 +1,74 @@ +/* meow_ui_nav.c — App 侧 M3 导航上报封装(SDK 公共件;见 meow_ui_nav.h)。 + * + * 只做「组装 m_nav_t + 经 meow_notify_ipc 一次性发送 MSG_NAV」,不维护连接/订阅循环; + * launcher 主循环单写者消费(回复 ACK)。全部失败路径优雅降级(无 socket/写失败/超时 + * → 静默返回,不崩)。状态(栈深/顶层/app_id)仅进程内缓存,无并发写(App 页面线程调用)。 + */ +#include "meow_ui_nav.h" +#include "meow_ipc.h" +#include "meow_notify_ipc.h" /* meow_notify_ipc_send_raw(复用其一次性帧传输层) */ + +#include +#include +#include + +static char g_app_id[MEOW_APP_ID_MAX] = ""; +static int g_depth = 0; +static char g_top[MEOW_ACT_MAX] = ""; +static int g_inited = 0; + +/* app_id:env MEOW_LOG_APP(与 meow_ui_log/meow_notify_ipc 取值口径一致),缺省 "app"。 */ +static const char *nav_app_id(void) +{ + if (!g_app_id[0]) { + const char *s = getenv("MEOW_LOG_APP"); + snprintf(g_app_id, sizeof g_app_id, "%s", (s && s[0]) ? s : "app"); + } + return g_app_id; +} + +int meow_ui_nav_init(void) +{ + if (g_inited) return g_depth; + g_inited = 1; + nav_app_id(); + const char *chain = getenv("MEOW_CHAIN"); + if (chain && chain[0]) { + g_depth = 1; + for (const char *p = chain; *p; p++) if (*p == ';') g_depth++; + } + const char *act = getenv("MEOW_ACTIVITY"); + if (act && act[0]) snprintf(g_top, sizeof g_top, "%s", act); + return g_depth; +} + +int meow_ui_nav_depth(void) { meow_ui_nav_init(); return g_depth; } +const char *meow_ui_nav_top(void) { meow_ui_nav_init(); return g_top; } + +void meow_ui_nav_report(uint32_t depth, const char *top) +{ + meow_ui_nav_init(); + m_nav_t n; + memset(&n, 0, sizeof n); + snprintf(n.app_id, sizeof n.app_id, "%s", nav_app_id()); + n.nav_type = MEOW_NAV_REPORT; + n.depth = depth; + if (top) snprintf(n.current, sizeof n.current, "%s", top); + (void)meow_notify_ipc_send_raw(MSG_NAV, &n, (uint32_t)sizeof n, 1); + /* 进程内镜像:App 侧也维持当前栈深/顶层(跨 TU 只读用) */ + g_depth = (int)depth; + if (top) snprintf(g_top, sizeof g_top, "%s", top); +} + +void meow_ui_nav_request_exit(void) +{ + meow_ui_nav_init(); + m_nav_t n; + memset(&n, 0, sizeof n); + snprintf(n.app_id, sizeof n.app_id, "%s", nav_app_id()); + n.nav_type = MEOW_NAV_EXIT; + n.depth = 0; + (void)meow_notify_ipc_send_raw(MSG_NAV, &n, (uint32_t)sizeof n, 1); + g_depth = 0; + g_top[0] = 0; +} diff --git a/meow_ui_nav.h b/meow_ui_nav.h new file mode 100644 index 0000000..d786588 --- /dev/null +++ b/meow_ui_nav.h @@ -0,0 +1,42 @@ +/* meow_ui_nav.h — App 侧 M3 导航上报封装(SDK 公共件)。 + * + * App 用它把「应用内页面栈深/顶层 activity」经 IPC 报给 launcher(MSG_NAV REPORT), + * 以及在根屏按返回时请求退出(MSG_NAV EXIT);launcher 据此维护每 app 导航栈(纯上报, + * 回退边界由 launcher 统一决定)。App 不渲染系统 UI(FRAMEWORK_DESIGN §3 / MEOW_APP_SDK §6.1)。 + * + * 命名:launcher 侧已占用 meow_nav_*(app_launcher.c),SDK 侧统一 meow_ui_nav_* 前缀, + * 避免两态同符号在 launcher(链 meow_ui.a + app_launcher.c)出现重复定义。 + * + * 冷启动链路:launcher 经 MEOW_ACTIVITY= / MEOW_CHAIN=;;...; 注入; + * meow_ui_nav_init() 读之初始化栈深/顶层。App 自行 push/pop 页面后经 meow_ui_nav_report() + * 上报新深度/顶层。 + */ +#ifndef MEOW_UI_NAV_H +#define MEOW_UI_NAV_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* 从 env 初始化(幂等):MEOW_CHAIN 段数→栈深;MEOW_ACTIVITY→顶层 activity_id。 + * 未注入(裸启动)→ 深度 0、顶层空串。app_id 取 env MEOW_LOG_APP(缺省 "app")。 + * 返回当前逻辑深度(0=未注入/裸启动)。 */ +int meow_ui_nav_init(void); + +/* 当前栈深 / 顶层 activity_id(meow_ui_nav_init 后为其初值;App 上报后更新)。 */ +int meow_ui_nav_depth(void); +const char *meow_ui_nav_top(void); + +/* 上报 MEOW_NAV_REPORT:App 弹/推自己的屏后调用(depth=当前逻辑深度 1..16,top=顶层)。 + * best-effort:launcher 不在/无 socket 静默降级,不崩。 */ +void meow_ui_nav_report(uint32_t depth, const char *top); + +/* 上报 MEOW_NAV_EXIT:App 在根屏按返回,请求 launcher 终止应用回主屏。best-effort。 */ +void meow_ui_nav_request_exit(void); + +#ifdef __cplusplus +} +#endif +#endif /* MEOW_UI_NAV_H */ diff --git a/meow_ui_sys.c b/meow_ui_sys.c index aea79f7..b5fade9 100644 --- a/meow_ui_sys.c +++ b/meow_ui_sys.c @@ -15,6 +15,7 @@ #include "meow_ui_sys.h" #include "meow_ui_internal.h" #include "meow_ui_vol.h" +#include "meow_notify_ipc.h" /* meow_notify_ipc_notif_open:App 前台 #10.5 上报 */ #include #include @@ -256,12 +257,18 @@ int meow_ui_sys_statusbar_height(void) return meow_ui_statusbar_collapsed() ? MUI_SBAR_COLLAPSE_H : MUI_SBAR_H; } -void meow_ui_sys_notif_pull(void) +void meow_ui_sys_notif_pull(int origin_y) { - /* 系统级「下拉展开通知中心」手势接线(M2.5x):输入层识别出「起始于顶部热区的 - * SWIPE_DOWN」后路由到此。通知中心 UI 为后续里程碑——此处先落记录(避免哑无痕); - * 届时由对应代理在该入口挂真实展开逻辑(IPC SHOW_ACTIVITY / 覆盖层渲染)。 */ - meow_log(MEOW_LOG_WARN, "notif_pull: notification center TBD (wired)"); + /* 系统级「下拉展开通知中心」手势接线(FRAMEWORK_DESIGN §10.5):输入层识别出「起始于 + * 顶部热区的 SWIPE_DOWN」后路由到此。App 进程只做检测+上报(不发事件给 on_swipe), + * 通知中心仍由 launcher 渲染——此处经 IPC 发 MSG_NOTIF_OPEN 请求 launcher 展开叠加会话 + * (SIGSTOP App + 读回其帧 + 叠加渲染通知中心)。best-effort:无 socket/launcher 不在 + * → meow_notify_ipc_notif_open 静默降级。 */ + static uint32_t seq = 0; + if (origin_y < 0) origin_y = 0; + meow_notify_ipc_notif_open(NULL, ++seq, 0, (uint16_t)origin_y, + (uint16_t)meow_ui_statusbar_hotzone(), + (uint8_t)(g_fullscreen ? 1 : 0)); } int meow_ui_sys_statusbar_force(void) diff --git a/meow_ui_sys.h b/meow_ui_sys.h index 4acfaa3..2a67ef9 100644 --- a/meow_ui_sys.h +++ b/meow_ui_sys.h @@ -64,10 +64,11 @@ int meow_ui_sys_statusbar_active(void); * MUI_SBAR_COLLAPSE_H(非满 28,app 内容可上移);否则 0(供 meow_ui_statusbar_height 用)。 */ int meow_ui_sys_statusbar_height(void); -/* 系统级「下拉展开通知中心」手势挂钩(M2.5x 接线):输入层识别出「起始于顶部热区的 - * SWIPE_DOWN」后路由到这里。通知中心 UI 为后续里程碑,此入口作记录/占位;届时由对应 - * 代理在此挂真实展开逻辑(IPC SHOW_ACTIVITY / 覆盖层渲染)。 */ -void meow_ui_sys_notif_pull(void); +/* 系统级「下拉展开通知中心」手势挂钩(§10.5):输入层识别出「起始于顶部热区的 SWIPE_DOWN」 + * 后路由到这里(origin_y=手势按下原点 Y,供 launcher 复核是否在顶部热区)。App 进程只做 + * 检测+上报:经 IPC 发 MSG_NOTIF_OPEN 请求 launcher 展开通知中心叠加会话;**App 不渲染系统 UI**。 + * best-effort:无 socket/launcher 不在 → 静默降级。 */ +void meow_ui_sys_notif_pull(int origin_y); /* 自上次渲染后状态栏数据是否已变(1s 轻刷/显隐切换置位);读取即清除。 * 渲染后端 present() 用它决定是否重画顶部条(配合脏区/整帧判断)。 */ diff --git a/test_ime_fix b/test_ime_fix index 14f793e..c4d3ce7 100755 Binary files a/test_ime_fix and b/test_ime_fix differ diff --git a/test_ime_fix.c b/test_ime_fix.c index dd9593e..d9034b4 100644 --- a/test_ime_fix.c +++ b/test_ime_fix.c @@ -8,7 +8,7 @@ * gcc -Wall -Wextra -o test_ime_fix test_ime_fix.c test_host_stub.c \ * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ * meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \ - * meow_ui_draw.c meow_ui_view.c meow_ui_img.c stb_image.c \ + * meow_ui_draw.c meow_ui_view.c meow_ui_img.c meow_notify_ipc.c stb_image.c \ * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm * (meow_ui_widget.c 新 W_LISTVIEW 引用 meow_ui_img_scaled → 需链 meow_ui_img.c+stb_image.c+-lm) */ diff --git a/test_meow_ui_widgets b/test_meow_ui_widgets index 2e3cf4f..acde7c0 100755 Binary files a/test_meow_ui_widgets and b/test_meow_ui_widgets differ diff --git a/test_meow_ui_widgets.c b/test_meow_ui_widgets.c index 6dae3d2..c0f04c5 100644 --- a/test_meow_ui_widgets.c +++ b/test_meow_ui_widgets.c @@ -5,7 +5,7 @@ * gcc -o test_meow_ui_widgets test_meow_ui_widgets.c test_host_stub.c \ * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ * meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \ - * meow_ui_draw.c meow_ui_view.c meow_ui_img.c stb_image.c \ + * meow_ui_draw.c meow_ui_view.c meow_ui_img.c meow_notify_ipc.c stb_image.c \ * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm * 运行:MEOW_UI_DUMP=/tmp/b2.ppm ./test_meow_ui_widgets (可选,dump 屏幕) * diff --git a/test_notif_open b/test_notif_open new file mode 100755 index 0000000..6e4a004 Binary files /dev/null and b/test_notif_open differ diff --git a/test_notif_open.c b/test_notif_open.c new file mode 100644 index 0000000..394d34f --- /dev/null +++ b/test_notif_open.c @@ -0,0 +1,191 @@ +/* test_notif_open.c — 宿主验证 §10.5「App 前台划出通知中心」的 SDK 侧上报链路: + * 1. m_notif_open_t / m_notif_close_t 帧组装解析(round-trip)。 + * 2. meow_notify_ipc_notif_open/close 经真实 AF_UNIX socket 发到 mock launcher, + * 服务端收到 MSG_NOTIF_OPEN / MSG_NOTIF_CLOSE 且字段正确。 + * 3. meow_ui_sys_notif_pull(origin_y)(顶部下滑系统手势钩子)→ 经 IPC 发 MSG_NOTIF_OPEN, + * 服务端收到(origin_y/hotzone 由 SDK 填)。不渲染系统 UI——纯上报。 + * 4. meow_notify_ipc_send_raw 的 want_ack 路径(服务端回 ACK,不阻塞)。 + * + * 编译(宿主 gcc,链 meow_notify_ipc + meow_ui_sys + gfx/theme/vol/log + 打桩 font/global): + * gcc -Wall -o test_notif_open test_notif_open.c test_host_stub.c \ + * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ + * meow_notify_ipc.c \ + * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm + * 运行:./test_notif_open + */ +#include "meow_ui.h" +#include "meow_ui_sys.h" /* meow_ui_sys_notif_pull */ +#include "meow_notify_ipc.h" /* meow_notify_ipc_notif_open/close + send_raw */ +#include "meow_ipc.h" /* MSG_NOTIF_OPEN/CLOSE + m_notif_open_t/m_notif_close_t */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TEST_SOCK "/tmp/meow_notif_open_test.sock" +#define ACK_CODE 0x4D00u /* 普通测试 ACK 载荷标记 */ + +static int g_srv_fd = -1; +static int g_stop = 0; + +/* 服务端捕获到的帧(互斥保护) */ +static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER; +static struct { + uint16_t type; /* 收到的帧 type */ + uint16_t plen; /* payload 长 */ + uint8_t pay[256]; +} g_rx[8]; +static int g_rx_n = 0; + +static int srv_send(int fd, uint16_t type, uint8_t *pay, uint32_t len) +{ + uint8_t hdr[MEOW_IPC_HDR_LEN]; + uint32_t magic = MEOW_IPC_MAGIC; uint16_t ver = MEOW_IPC_VERSION; + uint32_t ack = ACK_CODE; + memcpy(hdr+0,&magic,4); memcpy(hdr+4,&ver,2); memcpy(hdr+6,&type,2); memcpy(hdr+8,&ack,4); memcpy(hdr+12,&len,4); + size_t off = 0; + while (off < sizeof hdr) { ssize_t w = write(fd, hdr+off, sizeof hdr-off); if (w<=0) return -1; off+=(size_t)w; } + if (len && pay) { off=0; while (off 256) goto next; + pthread_mutex_lock(&g_mu); + if (g_rx_n < 8) { + uint8_t pay[256]; memset(pay,0,sizeof pay); + off=0; while(off=sizeof(m_notif_open_t)) { + m_notif_open_t o; memcpy(&o,pay,sizeof o); + if (strcmp(o.app_id,"os.meow.hello")==0 && o.event_seq==7 && o.origin_x==3 && + o.origin_y==8 && o.hotzone==28 && o.flags==0) { + printf("PASS notif_open: type=0x%04x app=%s seq=%u oy=%u hz=%u\n", t, o.app_id, o.event_seq, o.origin_y, o.hotzone); + } else { printf("FAIL notif_open: field mismatch\n"); fail=1; } + } else { printf("FAIL notif_open: unexpected frame type=0x%04x plen=%u\n", t, pl); fail=1; } + + /* 2) meow_ui_sys_notif_pull(5):顶部下滑系统手势 → SDK 上报 MSG_NOTIF_OPEN(不渲染系统 UI)。 + * app_id 走 env MEOW_LOG_APP;hotzone=状态栏激活(28);fullscreen=0 → flags=0。 */ + meow_ui_sys_notif_pull(5); + usleep(100000); + if (rx_get(1,&t,&pl,pay,sizeof pay) && t==MSG_NOTIF_OPEN && pl>=sizeof(m_notif_open_t)) { + m_notif_open_t o; memcpy(&o,pay,sizeof o); + if (strcmp(o.app_id,"os.meow.hello")==0 && o.origin_y==5 && o.hotzone==28 && o.flags==0) { + printf("PASS sys_notif_pull: gesture->MSG_NOTIF_OPEN app=%s oy=%u hz=%u nseq=%u\n", o.app_id, o.origin_y, o.hotzone, o.event_seq); + } else { printf("FAIL sys_notif_pull: field mismatch\n"); fail=1; } + } else { printf("FAIL sys_notif_pull: unexpected frame type=0x%04x plen=%u\n", t, pl); fail=1; } + + /* 3) meow_notify_ipc_notif_close 发 MSG_NOTIF_CLOSE */ + meow_notify_ipc_notif_close("os.meow.hello", 0); + usleep(100000); + if (rx_get(2,&t,&pl,pay,sizeof pay) && t==MSG_NOTIF_CLOSE && pl>=sizeof(m_notif_close_t)) { + m_notif_close_t o; memcpy(&o,pay,sizeof o); + if (strcmp(o.app_id,"os.meow.hello")==0 && o.reason==0) { + printf("PASS notif_close: type=0x%04x app=%s reason=%u\n", t, o.app_id, o.reason); + } else { printf("FAIL notif_close: field mismatch\n"); fail=1; } + } else { printf("FAIL notif_close: unexpected frame type=0x%04x plen=%u\n", t, pl); fail=1; } + + /* 4) 帧组装解析 round-trip:构造 m_notif_open_t 序列化 → 解析恢复各字段。 */ + { + m_notif_open_t a; memset(&a,0,sizeof a); + snprintf(a.app_id,sizeof a.app_id,"os.meow.hello"); + a.event_seq=9; a.origin_x=2; a.origin_y=32; a.hotzone=4; a.flags=1; + m_notif_open_t b; memset(&b,0,sizeof b); + memcpy(&b,&a,sizeof a); + if (b.event_seq==9 && b.origin_y==32 && b.hotzone==4 && b.flags==1 && b.app_id[0]=='o') + printf("PASS roundtrip: m_notif_open_t 44B (sz=%zu)\n", sizeof a); + else { printf("FAIL roundtrip\n"); fail=1; } + } + + /* 5) meow_notify_ipc_send_raw want_ack 路径(无效帧类型,服务端回 ERR? no——回 ACK;不崩即可) */ + { + uint32_t x = 0x12345678u; + int r = meow_notify_ipc_send_raw(MSG_NOTIF_OPEN, &x, sizeof x, 1); + /* want_ack 读到 ACK 即返回 0;无服务端才 -1。这里服务端在跑,应为 0。 */ + printf("%s send_raw(MSG_NOTIF_OPEN, want_ack) -> %d\n", r==0?"PASS":"FAIL", r); + if (r!=0) fail=1; + } + + g_stop = 1; + pthread_join(th, NULL); /* 服务端线程自关 lfd 并退出 */ + + if (fail) { printf("RESULT: FAIL\n"); return 1; } + printf("RESULT: ALL PASS\n"); + return 0; +} diff --git a/test_p1p2p3_repro.c b/test_p1p2p3_repro.c index d779373..27438eb 100644 --- a/test_p1p2p3_repro.c +++ b/test_p1p2p3_repro.c @@ -25,7 +25,7 @@ * test_host_stub.c meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ * meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \ * meow_ui_img.c meow_ui_config.c meow_ui_notify.c meow_notify_ipc.c meow_ui_draw.c \ - * meow_ui_view.c stb_image.c \ + * meow_ui_view.c meow_ui_nav.c meow_ui_media.c stb_image.c \ * ../hello_app/ui/home/home.c ../hello_app/ui/full.c ../hello_app/ui/demo/demo.c \ * ../hello_app/ui/demo/list/list.c ../hello_app/ui/common/chrome.c \ * ../hello_app/logic/notify.c ../hello_app/logic/state.c \ diff --git a/test_sb_collapse b/test_sb_collapse new file mode 100755 index 0000000..08d259d Binary files /dev/null and b/test_sb_collapse differ diff --git a/test_sb_collapse.c b/test_sb_collapse.c index ffbc7cb..3967769 100644 --- a/test_sb_collapse.c +++ b/test_sb_collapse.c @@ -7,10 +7,10 @@ * collapse_offset() 随时间推进 → 28(收合出屏);set_collapsed(0) → offset 回 0(恢复)。 * 4. 收合 inset:collapsed → meow_ui_statusbar_height()==MUI_SBAR_COLLAPSE_H(4)。 * - * 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log + 打桩 font/global): + * 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log + 打桩 font/global + meow_notify_ipc): * gcc -o test_sb_collapse test_sb_collapse.c test_host_stub.c \ - * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ - * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST + * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c meow_notify_ipc.c \ + * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread */ #include "meow_ui.h" #include "meow_ui_internal.h" diff --git a/test_sdk_fixes b/test_sdk_fixes new file mode 100755 index 0000000..59eb824 Binary files /dev/null and b/test_sdk_fixes differ diff --git a/test_sdk_fixes.c b/test_sdk_fixes.c index 311396c..3392e6f 100644 --- a/test_sdk_fixes.c +++ b/test_sdk_fixes.c @@ -10,7 +10,7 @@ * gcc -Wall -o test_sdk_fixes test_sdk_fixes.c test_host_stub.c \ * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ * meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \ - * meow_ui_draw.c meow_ui_view.c \ + * meow_ui_draw.c meow_ui_view.c meow_notify_ipc.c \ * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread */ #include "meow_ui.h"