feat(sdk+launcher): SDK nav/media封装 + §10.5 App前台唤起通知中心 (agent-206) — meow_ui_nav.c(env栈初始化+report/request_exit) + meow_ui_media.c(懒连断线重连, meta上报+playctl接收) + MSG_NOTIF_OPEN=0x28/CLOSE=0x29(0x25/26已被占); SDK顶部下滑手势→IPC; launcher App前台会话叠加通知中心(SIGSTOP→读帧→接管输入→恢复SIGCONT, 面板内App崩溃即关); hello接线验证
meow_ui.a→9a8eb7ef; hello→fc299577; launcher→b33da20a; 宿主5套件+新test_notif_open+qemu分发 全过 出入: SDK符号用meow_ui_前缀防与launcher内同名冲突; 叠加会话真机待验; §10.5文档旧号段已过时
This commit is contained in:
1 parent
21519ce2e4
commit
9b4195724c
37 files changed
+1042
-21
No files matched your search
@@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in new issue
Block a user