feat(launcher): 媒体会话+Now Playing (agent-204) — MSG_META(0x72)/MSG_PLAYCTL(0x73)+meow_media.c(最近活跃源=active_seq净序,30s超时回收); 通知中心顶部媒体卡片(封面缓存+进度seek+控制键); 主界面中部/右缘左滑进 Now Playing 大页(封面+元数据+进度+控制+歌词占位), 左缘返回; 空态完备

launcher a42171a1→ddfe616e; 宿主 test_media ALL PASS + qemu 端到端(META注册→PLAYCTL下发闭环)
出入: 卡片以媒体会话为权威非通知; PLAYCTL 走会话fd(断连防御留后续)
This commit is contained in:
MeowOS committed 2026-08-31 06:51:04 +08:00
1 parent 8012948b26
commit 32543ac616
15 files changed
+1429 -34

No files matched your search

+154
View File
@@ -0,0 +1,154 @@
/* test_ipc_harness.c — 宿主 IPC 服务端模拟(等价 launcher ipc_dispatch 的媒体子集).
*
* 用 meow_media.c + meow_ipc.h 复刻 launcher 的 IPC 服务器对媒体消息的处理,
* 供「qemu-mipsel meow_ipc listen/meta/playctl」端到端验证:
* listen → HELLO(MF_PLAYBACK)+META 注册源并等待下发;
* meta → 上报 META(作为新源/更新);
* playctl → 上传控制,服务端转发给「最近活跃会话」fd。
* 运行:gcc test_ipc_harness.c meow_media.c -o /tmp/meow_srv ; /tmp/meow_srv /tmp/e2e.sock
*/
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <poll.h>
#include "meow_ipc.h"
#include "meow_media.h"
#define MAXCLI 8
typedef struct {
int fd;
uint8_t hdr[MEOW_IPC_HDR_LEN]; int hdr_n;
uint8_t pay[MEOW_IPC_MAX]; uint32_t pay_tot, pay_n;
char app_id[MEOW_APP_ID_MAX];
} conn;
static conn g_c[MAXCLI];
static size_t srv_send(int fd, uint16_t type, uint32_t req, const void *payload, uint32_t len)
{
if (fd < 0) return 0;
uint8_t h[MEOW_IPC_HDR_LEN];
uint32_t magic = MEOW_IPC_MAGIC; uint16_t ver = MEOW_IPC_VERSION;
memcpy(h+0,&magic,4); memcpy(h+4,&ver,2); memcpy(h+6,&type,2); memcpy(h+8,&req,4); memcpy(h+12,&len,4);
size_t off=0;
while (off<sizeof h){ ssize_t w=write(fd,h+off,sizeof h-off); if(w<=0)return off; off+=(size_t)w; }
if(len&&payload){ size_t p=0; while(p<len){ ssize_t w=write(fd,(const uint8_t*)payload+p,len-p); if(w<=0)break; p+=(size_t)w; } off+=p; }
return off;
}
static conn *find_slot(int fd) { for (int i=0;i<MAXCLI;i++) if (g_c[i].fd==fd) return &g_c[i]; return NULL; }
static conn *alloc_slot(int fd){ for (int i=0;i<MAXCLI;i++) if (g_c[i].fd==-1){ g_c[i].fd=fd; g_c[i].hdr_n=0; g_c[i].pay_tot=g_c[i].pay_n=0; g_c[i].app_id[0]=0; return &g_c[i]; } return NULL; }
static void handle(conn *c)
{
uint16_t type; uint32_t req;
memcpy(&type, c->hdr+6, 2); memcpy(&req, c->hdr+8, 4);
switch (type) {
case MSG_HELLO: {
m_hello_t h; memset(&h,0,sizeof h); memcpy(&h,c->pay,c->pay_n<sizeof h?c->pay_n:sizeof h);
snprintf(c->app_id, sizeof c->app_id, "%s", h.app_id);
m_ack_t a; a.code=MEOW_OK; a.status=1; srv_send(c->fd,MSG_ACK,req,&a,sizeof a);
printf("SRV: HELLO app=%s flags=%u -> ACK\n", c->app_id, h.flags); break;
}
case MSG_META: {
m_meta_t m; memset(&m,0,sizeof m); memcpy(&m,c->pay,c->pay_n<sizeof m?c->pay_n:sizeof m);
m.app_id[MEOW_APP_ID_MAX-1]=0; m.title[127]=0; m.artist[127]=0; m.album[127]=0; m.artwork[191]=0; m.lyric[191]=0;
int rc = meow_media_meta(&m, c->fd);
m_ack_t a; a.code=MEOW_OK; a.status=(uint32_t)rc; srv_send(c->fd,MSG_ACK,req,&a,sizeof a);
const meow_media_session_t *cur = meow_media_current();
printf("SRV: META app=%s state=%u rc=%d | current=%s\n", m.app_id, m.state, rc,
cur?cur->app_id:"(none)");
break;
}
case MSG_PLAYCTL: {
m_playctl_t p; memset(&p,0,sizeof p); memcpy(&p,c->pay,c->pay_n<sizeof p?c->pay_n:sizeof p);
int rc = meow_media_playctl(p.cmd, p.seek_ms);
m_ack_t a; a.code=MEOW_OK; a.status=(uint32_t)rc; srv_send(c->fd,MSG_ACK,req,&a,sizeof a);
printf("SRV: PLAYCTL cmd=%u seek=%lld rc=%d (target current)\n", p.cmd, (long long)p.seek_ms, rc);
break;
}
default: {
m_err_t e; memset(&e,0,sizeof e); e.code=MEOW_ERR_INVAL; e.msg_len=5; memcpy(e.msg,"nope",5);
srv_send(c->fd,MSG_ERR,req,&e,sizeof e);
printf("SRV: unsupported type 0x%04x\n", type); break;
}
}
}
/* 读一个连接可用字节并组装帧(>=1 完整帧即 dispatch)。返回 0=ok,-1=连接关闭。 */
static int conn_read(conn *c)
{
for (;;) {
if ((unsigned)c->hdr_n < MEOW_IPC_HDR_LEN) {
ssize_t r = read(c->fd, c->hdr + c->hdr_n, MEOW_IPC_HDR_LEN - c->hdr_n);
if (r < 0) { if (errno==EAGAIN) return 0; return -1; }
if (r == 0) return -1;
c->hdr_n += (int)r;
if ((unsigned)c->hdr_n < MEOW_IPC_HDR_LEN) return 0;
uint32_t magic, len; uint16_t ver;
memcpy(&magic,c->hdr,4); memcpy(&ver,c->hdr+4,2); memcpy(&len,c->hdr+12,4);
if (magic!=MEOW_IPC_MAGIC||ver!=MEOW_IPC_VERSION||len>MEOW_IPC_MAX) return -1;
c->pay_tot=len; c->pay_n=0;
if (len==0){ handle(c); c->hdr_n=0; continue; }
}
if (c->pay_n < c->pay_tot) {
ssize_t r = read(c->fd, c->pay + c->pay_n, c->pay_tot - c->pay_n);
if (r < 0){ if (errno==EAGAIN) return 0; return -1; }
if (r==0) return -1;
c->pay_n += (uint32_t)r;
if (c->pay_n < c->pay_tot) return 0;
}
handle(c);
c->hdr_n=0; c->pay_n=0;
}
}
int main(int argc, char **argv)
{
const char *path = (argc>1)?argv[1]:"/tmp/meow_ipc_e2e.sock";
for (int i=0;i<MAXCLI;i++) g_c[i].fd=-1;
meow_media_ops ops;
ops.send=srv_send; ops.log=NULL; meow_media_init(&ops);
int lfd=socket(AF_UNIX,SOCK_STREAM,0);
unlink(path);
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(lfd,(struct sockaddr*)&a,sizeof a)<0){ perror("bind"); return 1; }
chmod(path,0666);
if (listen(lfd,8)<0){ perror("listen"); return 1; }
fcntl(lfd,F_SETFL,O_NONBLOCK);
printf("SRV: listening %s\n", path); fflush(stdout);
struct pollfd fds[MAXCLI+1]; int nfds=1;
fds[0].fd=lfd; fds[0].events=POLLIN; fds[0].revents=0;
while (1) {
fds[0].fd=lfd; fds[0].events=POLLIN;
int n=1;
for (int i=0;i<MAXCLI;i++) if (g_c[i].fd>=0){ fds[n].fd=g_c[i].fd; fds[n].events=POLLIN; fds[n].revents=0; n++; }
nfds=n;
int r=poll(fds,nfds,400);
if (r<0){ if(errno==EINTR)continue; break; }
if (fds[0].revents&POLLIN){
for(;;){ int c=accept(lfd,NULL,NULL); if(c<0)break; fcntl(c,F_SETFL,O_NONBLOCK);
if(!alloc_slot(c)){ close(c); printf("SRV: reject conn (full)\n"); } else printf("SRV: accept fd=%d\n", c); }
}
for (int i=1;i<nfds;i++){
int ev=fds[i].revents;
if (ev&(POLLIN|POLLHUP|POLLERR|POLLNVAL)){
conn *c=find_slot(fds[i].fd);
if (!c) continue;
if (conn_read(c)<0){ printf("SRV: conn %d closed\n", c->fd); close(c->fd); c->fd=-1; }
}
}
fflush(stdout);
}
close(lfd); unlink(path);
return 0;
}