diff --git a/patch_v2/meow_launcherd/app_launcher.c b/patch_v2/meow_launcherd/app_launcher.c index b8949d1..1dec427 100644 --- a/patch_v2/meow_launcherd/app_launcher.c +++ b/patch_v2/meow_launcherd/app_launcher.c @@ -771,6 +771,50 @@ static void app_overlay_statusbar(void) /* (definition follows) */ +#define MEOW_CRASH_SOCK "/tmp/meow_crash.sock" /* meow_crash 崩溃上报 socket(best-effort,见 meow_crash.md §2)*/ + +/* App 异常退出(信号杀 / 非 0 退出码)→ best-effort 直报 meow_crash。 + * 格式:`app sig=` / `app exit=`(参照 svcd notify_crash(),但用 app 域前缀)。 + * 全程非阻塞:socket O_NONBLOCK;connect 失败即弃;EINPROGRESS 短 poll(POLLOUT) ≤150ms 排空, + * 仍不可写即丢弃;单行小消息一次 write(失败即弃),绝不重试。meow_crash 不在线 → 静默丢, + * 绝不拖垮 launcher。调用方负责只对异常退出调用(干净 exit(0) 不上报)。 */ +static void notify_app_crash(const char *pkg, int status) +{ + const char *kind; + int num; + if (WIFSIGNALED(status)) { kind = "sig"; num = WTERMSIG(status); } + else { kind = "exit"; num = WIFEXITED(status) ? WEXITSTATUS(status) : 1; } + + char msg[192]; + int len = snprintf(msg, sizeof msg, "app %s %s=%d\n", pkg, kind, num); + if (len <= 0) return; + + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) return; + int fl = fcntl(fd, F_GETFL, 0); + if (fl >= 0) fcntl(fd, F_SETFL, fl | O_NONBLOCK); + + struct sockaddr_un a; + memset(&a, 0, sizeof a); + a.sun_family = AF_UNIX; + strncpy(a.sun_path, MEOW_CRASH_SOCK, sizeof a.sun_path - 1); + + if (connect(fd, (struct sockaddr *)&a, sizeof a) != 0) { + if (errno == EINPROGRESS || errno == EWOULDBLOCK) { + struct pollfd pf; + pf.fd = fd; pf.events = POLLOUT; pf.revents = 0; + int r = poll(&pf, 1, 150); + if (r <= 0 || !(pf.revents & POLLOUT)) { close(fd); return; } + } else { + close(fd); + return; /* crash 不在线:静默丢 */ + } + } + ssize_t w = write(fd, msg, (size_t)len); + (void)w; + close(fd); +} + int launch_app(struct app *a) { logmsg("launch: %s (%s)", a->name, a->exec); @@ -939,7 +983,7 @@ int launch_app(struct app *a) meow_write_app_state(a->immersive == '1' ? 1 : 0); /* watchdog for virtual back key / gestures */ - char wd1[192], wd2[192], pidstr[16]; + char wd1[192], pidstr[16]; pid_t wpid = 0; snprintf(pidstr, sizeof pidstr, "%d", pid); if (access(WATCHDOG1, X_OK) == 0) @@ -1134,6 +1178,11 @@ int launch_app(struct app *a) /* 顶条收合/恢复过渡期把循环睡得短些(~20ms),驱动更平滑的上移出屏;稳定后回 100ms。 */ usleep(meow_ui_statusbar_animating() ? 20000 : 100000); } + /* M2.x:App 异常退出(信号杀 / 非 0 退出码)→ best-effort 直报 meow_crash。 + * 判据与 svcd reap_children 一致:failed = !(WIFEXITED && WEXITSTATUS==0)。干净 exit(0) + * 不上报;sigterm 关停/电源菜单重启路径 status 保持 0(未 waitpid)→ 视为干净退出不上报。 */ + if (!(WIFEXITED(status) && WEXITSTATUS(status) == 0)) + notify_app_crash(a->id, status); if (wpid > 0) { /* reap the watchdog: SIGTERM then wait up to 2s (it exits when the * app dies; bounded so we never hang if it is stuck) */ diff --git a/patch_v2/meow_launcherd/meow_ipc.c b/patch_v2/meow_launcherd/meow_ipc.c index ae974f8..ad5b299 100644 --- a/patch_v2/meow_launcherd/meow_ipc.c +++ b/patch_v2/meow_launcherd/meow_ipc.c @@ -158,7 +158,6 @@ static int cmd_declare(const char *app_id, int nent, char **ents) strncpy(d.app_id, app_id, MEOW_APP_ID_MAX - 1); int n = 0; char *p = d.entries; - int cap = (int)sizeof(d.entries); for (int i = 0; i < nent && n < 8; i++) { size_t l = strlen(ents[i]); if (l + 1 > (size_t)(d.entries + sizeof d.entries - p)) break; @@ -272,6 +271,47 @@ static int cmd_dismiss(uint32_t id) return 1; } +/* 查询当前内存活跃通知集 → MSG_NOTIFY_QUERY,期望 MSG_NOTIFY_QUERY_RESULT(打印 JSON 文本)。 + * 可选 过滤(缺省=全部)。 */ +static int cmd_querynotif(const char *app_id) +{ + int fd = connect_path(g_sock); + if (fd < 0) return 1; + uint8_t rbuf[MEOW_IPC_MAX]; + int rt = xchg(fd, MSG_NOTIFY_QUERY, 8, app_id, app_id ? (uint32_t)strlen(app_id) : 0, + rbuf, sizeof rbuf); + close(fd); + if (rt == MSG_NOTIFY_QUERY_RESULT) { + m_notify_query_result_t r; + memcpy(&r, rbuf, sizeof r); + uint32_t tl = r.text_len < sizeof r.text ? r.text_len : sizeof r.text - 1; + printf("QUERY ok: code=%d len=%u\n%s\n", r.code, (unsigned)tl, r.text); + return r.code == MEOW_OK ? 0 : 1; + } + if (rt == MSG_ERR) { print_err((const m_err_t *)rbuf); return 1; } + fprintf(stderr, "QUERY: unexpected resp type 0x%04x\n", rt); + return 1; +} + +/* 一键清除(保留 ongoing/media/progress)→ MSG_NOTIFY_DISMISS_ALL,期望 MSG_ACK(status=清除数)。 */ +static int cmd_dismissall(void) +{ + int fd = connect_path(g_sock); + if (fd < 0) return 1; + uint8_t rbuf[256]; + int rt = xchg(fd, MSG_NOTIFY_DISMISS_ALL, 9, NULL, 0, rbuf, sizeof rbuf); + close(fd); + if (rt == MSG_ACK) { + m_ack_t a; + memcpy(&a, rbuf, sizeof a); + printf("DISMISS_ALL ok: code=%d cleared=%u\n", a.code, a.status); + return a.code == MEOW_OK ? 0 : 1; + } + if (rt == MSG_ERR) { print_err((const m_err_t *)rbuf); return 1; } + fprintf(stderr, "DISMISS_ALL: unexpected resp type 0x%04x\n", rt); + return 1; +} + int main(int argc, char **argv) { const char *env = getenv("MEOW_IPC_SOCK"); @@ -329,6 +369,13 @@ int main(int argc, char **argv) if (i + 1 >= argc) { fprintf(stderr, "dismiss needs \n"); return 1; } return cmd_dismiss((uint32_t)strtoul(argv[i + 1], NULL, 0)); } + if (!strcmp(cmd, "querynotif")) { + const char *app = (i + 1 < argc) ? argv[i + 1] : NULL; + return cmd_querynotif(app); + } + if (!strcmp(cmd, "dismissall")) { + return cmd_dismissall(); + } fprintf(stderr, "unknown cmd %s; try --help\n", cmd); return 1; } diff --git a/patch_v2/meow_launcherd/meow_ipc.h b/patch_v2/meow_launcherd/meow_ipc.h index adc337f..ad9dba2 100644 --- a/patch_v2/meow_launcherd/meow_ipc.h +++ b/patch_v2/meow_launcherd/meow_ipc.h @@ -45,6 +45,9 @@ enum { MSG_NOTIFY_DISMISS = 0x0022, /* ↑ 撤下通知 */ MSG_NOTIFY_LIST = 0x0023, /* ↓ 下发通知清单(重同步) */ MSG_NOTIFY_PROGRESS = 0x0024,/* ↑ 实时更新通知进度(仅改内存活跃槽,不落盘) */ + MSG_NOTIFY_QUERY = 0x0025, /* ↑ 查询内存活跃通知集(MeowPanel 实时化前置) */ + MSG_NOTIFY_DISMISS_ALL = 0x0026,/* ↑ 一键清除(保留 ongoing/media/progress 进行中,同通知中心 UI) */ + MSG_NOTIFY_QUERY_RESULT = 0x0027, /* ↓ 活跃通知集序列化文本(JSON,字段同 TF 归档) */ MSG_ACT_DECLARE = 0x0030, /* ↑ (重)声明路由表 */ MSG_ACT_QUERY = 0x0031, /* ↑ 解析 activity 链路 */ MSG_ACT_RESULT = 0x0032, /* ↓ 返回链路 */ @@ -169,6 +172,16 @@ typedef struct m_notify_progress { int32_t progress; /* 0-100;<0=清除进度条 */ } m_notify_progress_t; /* 8 B */ +/* MSG_NOTIFY_QUERY_RESULT ↓:活跃通知集序列化文本(JSON 数组;NUL 结尾)。 + * 字段与 TF 归档一致:id/app_id/category/summary/body/urgency/ts/progress/ongoing/media/ + * target_activity(+app_icon)。payload 定长 LE,整个结构 < MEOW_IPC_MAX。 */ +typedef struct m_notify_query_result { + uint32_t code; /* MEOW_OK / MEOW_ERR_* */ + uint16_t text_len; /* text 有效字节(不含 NUL) */ + uint16_t pad; + char text[MEOW_IPC_MAX - 12]; /* 序列化 JSON 数组 */ +} m_notify_query_result_t; /* 4096 B */ + /* ================= 专项服务 IPC(SYSTEM_FRAMEWORK_SERVICES §2.2/§2.3)= */ /* ---- MSG_PERF_QUERY 子集位(0=全部)---- */ diff --git a/patch_v2/meow_launcherd/meow_launcherd b/patch_v2/meow_launcherd/meow_launcherd index 0ebc32b..15c4d3d 100755 Binary files a/patch_v2/meow_launcherd/meow_launcherd and b/patch_v2/meow_launcherd/meow_launcherd differ diff --git a/patch_v2/meow_launcherd/meow_notif.c b/patch_v2/meow_launcherd/meow_notif.c index 85cae32..e73fd34 100644 --- a/patch_v2/meow_launcherd/meow_notif.c +++ b/patch_v2/meow_launcherd/meow_notif.c @@ -601,6 +601,121 @@ int meow_notif_update_progress(uint32_t id, int progress) return NOTIF_OK; } +/* ---- 一键清除(MSG_NOTIFY_DISMISS_ALL / 通知中心 UI 同语义)---- + * 红线(用户 2026-08-30,与 notif_center.c ncp_clear_all 的 mnp_clearable 同判据): + * ongoing 常驻 / media 媒体 / 进行中进度(progress>=0)一律保留;其余 dismiss(= 归档留档 + 释放槽)。 + * 顺序遍历固定 slot 表,dismiss 只释放当前槽,不会跳条。 */ +int meow_notif_clear_active(void) +{ + if (!g_inited) return -NOTIF_ERR_NOTINIT; + int n = 0; + for (int i = 0; i < MEOW_NOTIF_MAX_SLOTS; i++) { + meow_notif_t *s = &g_slots[i]; + if (!s->active || s->dismissed) continue; + if (s->ongoing || s->media || s->progress >= 0) continue; /* 保留红线项 */ + if (meow_notif_dismiss(s->id) == NOTIF_OK) n++; + } + notif_log("clear_active: dismissed %d (retained ongoing/media/progress)", n); + return n; +} + +/* ---- 活跃通知集 JSON 序列化(MeowPanel 实时化前置;字段同 TF 归档)---- */ + +/* JSON 字符串转义:`"`/`\` 反斜杠转义,<0x20 控制字符转 \\u00XX;写到 dst,返回字符数(不含 NUL)。 */ +static int json_escape(const char *s, char *dst, int cap) +{ + int o = 0; + if (!s) s = ""; + for (const char *p = s; *p; p++) { + unsigned char c = (unsigned char)*p; + if (c == '"' || c == '\\') { + if (o + 2 >= cap) break; + dst[o++] = '\\'; + dst[o++] = (char)c; + } else if (c < 0x20) { + const char *short_esc = NULL; + switch (c) { + case '\n': short_esc = "\\n"; break; + case '\r': short_esc = "\\r"; break; + case '\t': short_esc = "\\t"; break; + case '\b': short_esc = "\\b"; break; + case '\f': short_esc = "\\f"; break; + default: break; + } + if (short_esc) { + if (o + 2 >= cap) break; + dst[o++] = short_esc[0]; + dst[o++] = short_esc[1]; + } else { + if (o + 6 >= cap) break; + int n = snprintf(dst + o, cap - o, "\\u%04x", (unsigned)c); + if (n < 0) break; + o += n; + } + } else { + if (o + 1 >= cap) break; + dst[o++] = (char)c; + } + } + dst[o] = 0; + return o; +} + +/* 把一条活跃通知 append 成 JSON 对象到 buf+*o。返回 0=成功;-1=缓冲放不下(中止)。 */ +static int json_append_notif(meow_notif_t *s, char *buf, int buf_len, int *o) +{ + char a_app [2 + 2 * MEOW_NOTIF_APP_ID_MAX + 2]; + char a_cat [2 + 2 * MEOW_NOTIF_CAT_MAX + 2]; + char a_sum [2 + 2 * MEOW_NOTIF_SUMMARY_MAX + 2]; + char a_body[2 + 2 * MEOW_NOTIF_BODY_MAX + 2]; + char a_tgt [2 + 2 * MEOW_NOTIF_ACTIVITY_MAX + 2]; + char a_icon[2 + 2 * MEOW_NOTIF_ICON_MAX + 2]; + json_escape(s->app_id, a_app, sizeof a_app); + json_escape(s->category, a_cat, sizeof a_cat); + json_escape(s->summary, a_sum, sizeof a_sum); + json_escape(s->body, a_body, sizeof a_body); + json_escape(s->target_activity, a_tgt, sizeof a_tgt); + json_escape(s->app_icon, a_icon, sizeof a_icon); + int n = snprintf(buf + *o, buf_len - *o, + "{\"id\":%u,\"app_id\":\"%s\",\"category\":\"%s\",\"summary\":\"%s\"," + "\"body\":\"%s\",\"urgency\":%u,\"ts\":%llu,\"progress\":%d,\"ongoing\":%u," + "\"media\":%u,\"target_activity\":\"%s\",\"app_icon\":\"%s\"}", + (unsigned)s->id, a_app, a_cat, a_sum, a_body, + (unsigned)s->urgency, (unsigned long long)s->created_ms, (int)s->progress, + s->ongoing ? 1u : 0u, s->media ? 1u : 0u, a_tgt, a_icon); + if (n < 0) return -1; + if (*o + n >= buf_len) return -1; /* 放不下:中止(调用方保前缀截断) */ + *o += n; + return 0; +} + +int meow_notif_serialize_json(const char *app_id, char *buf, int buf_len) +{ + if (!g_inited) return -NOTIF_ERR_NOTINIT; + if (!buf || buf_len <= 0) return -NOTIF_ERR_INVAL; + int o = 0; + buf[0] = 0; + if (o + 1 >= buf_len) return 0; /* 放不下 '[' */ + buf[o++] = '['; + int first = 1; + for (int i = 0; i < MEOW_NOTIF_MAX_SLOTS; i++) { + meow_notif_t *s = &g_slots[i]; + if (!s->active || s->dismissed) continue; + if (app_id && app_id[0] && strcmp(s->app_id, app_id)) continue; + if (!first) { + if (o + 1 >= buf_len) break; + buf[o++] = ','; + } + if (json_append_notif(s, buf, buf_len, &o) != 0) break; /* 满:保留已完成前缀 */ + first = 0; + } + if (o + 1 < buf_len) buf[o++] = ']'; + buf[o] = 0; + notif_log("serialize_json: app=%s active=%d -> %dB", + app_id && app_id[0] ? app_id : "*", meow_notif_count(app_id), o); + return o; +} + /* ---- count ---- */ int meow_notif_count(const char *app_id) { diff --git a/patch_v2/meow_launcherd/meow_notif.h b/patch_v2/meow_launcherd/meow_notif.h index a7c0dad..62de2d4 100644 --- a/patch_v2/meow_launcherd/meow_notif.h +++ b/patch_v2/meow_launcherd/meow_notif.h @@ -132,6 +132,17 @@ void meow_notif_set_icon_cb(meow_notif_icon_cb cb); * 立即归档(落盘快照)并(非 ONGOING)释放槽——对应「里程碑落盘」。返回 NOTIF_OK/NOTIF_ERR_NOENT。 */ int meow_notif_update_progress(uint32_t id, int progress); +/* 一键清除(MSG_NOTIFY_DISMISS_ALL / 通知中心 UI 同语义):dismiss 所有「可清除」活跃通知 + * (非 ongoing / 非 media / 无进行中进度 progress<0——红线段一律保留)。每条 dismiss = 归档留档 + * + 释放槽。返回清除条数;<0=错误(NOTIF_ERR_*)。 */ +int meow_notif_clear_active(void); + +/* 把当前活跃通知集序列化为 JSON 数组文本写入 buf(≤buf_len,NUL 结尾)。app_id 空/NULL=全部, + * 非空=仅该 app 的活跃通知。字段同 TF 归档:id/app_id/category/summary/body/urgency/ts/ + * progress/ongoing/media/target_activity(+app_icon);ts=created_ms(epoch ms)。 + * 返回文本字节数(不含 NUL);<0=错误(NOTIF_ERR_*)。 */ +int meow_notif_serialize_json(const char *app_id, char *buf, int buf_len); + /* == TF 卡转储(通知#6 / M2c,NOTIFICATION_SYSTEM.md §5/§8 + MEOWOS_SPEC_REF §5.3)== * 把 dump_cb 接到 meow_notif_dump_tf 即把每条通知追加为一行 CSV 到 * <卡根>/MeowOS/notifications/notifications.log,并按日志总大小滚动截断 diff --git a/patch_v2/meow_launcherd/util.c b/patch_v2/meow_launcherd/util.c index 9b0f9d3..ae87f94 100644 --- a/patch_v2/meow_launcherd/util.c +++ b/patch_v2/meow_launcherd/util.c @@ -459,8 +459,9 @@ int ui_width(const char *s, int size) } -/* 文本高度(字库行高 or 回退 7×scale 估算),用于垂直居中 / 行高预算。 */ -static int ui_text_height(int size) +/* 文本高度(字库行高 or 回退 7×scale 估算),用于垂直居中 / 行高预算。 + * (拆分后无调用点,保留接口;unused 满足 -Wall 0 warning) */ +static int __attribute__((unused)) ui_text_height(int size) { if (mfont_ready) return meow_font_line_height(size); return 7 * ((size >= 16) ? 2 : 1); @@ -480,8 +481,9 @@ static int ui_text_height(int size) void meow_dirty_full(void) { meow_gfx_mark_full(); } -/* 清空脏集:SDK present 上屏后自清;launcher 侧无需再清(保留接口兼容)。 */ -static void meow_dirty_reset(void) { } +/* 清空脏集:SDK present 上屏后自清;launcher 侧无需再清(保留接口兼容)。 + * (拆分后无调用点,保留钩子;unused 满足 -Wall 0 warning) */ +static void __attribute__((unused)) meow_dirty_reset(void) { } /* 标记一个矩形脏:桥进 SDK 脏追踪(相交合并/超限转全屏由 SDK dirty_add 处理)。 */ @@ -891,12 +893,12 @@ static void ipc_enqueue_frame(struct meow_ipc_conn *c) static int ipc_conn_read(struct meow_ipc_conn *c) { for (;;) { - if (c->hdr_n < MEOW_IPC_HDR_LEN) { + 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 (c->hdr_n < MEOW_IPC_HDR_LEN) return 0; + if ((unsigned)c->hdr_n < MEOW_IPC_HDR_LEN) return 0; uint32_t magic, len; uint16_t ver; memcpy(&magic, c->hdr, 4); @@ -1191,6 +1193,33 @@ static void ipc_dispatch(struct meow_ipc_msg *m) logmsg("ipc: NOTIFY_PROGRESS nid=%u p=%d rc=%d -> ACK", p.nid, p.progress, rc); break; } + /* 查询当前内存活跃通知集(MeowPanel 实时化前置)。序列化在主循环出队后做(单写者), + * 绝不把通知存储/ring 读到 IPC 线程;应答经 MSG_NOTIFY_QUERY_RESULT 回 JSON 数组文本。 */ + case MSG_NOTIFY_QUERY: { + char filter[MEOW_APP_ID_MAX] = ""; + if (m->plen > 0) { + uint32_t cp = m->plen < MEOW_APP_ID_MAX - 1 ? m->plen : MEOW_APP_ID_MAX - 1; + memcpy(filter, m->payload, cp); + filter[MEOW_APP_ID_MAX - 1] = 0; + } + m_notify_query_result_t r; + memset(&r, 0, sizeof r); + int n = meow_notif_serialize_json(filter[0] ? filter : NULL, r.text, sizeof r.text); + r.code = (n >= 0) ? MEOW_OK : MEOW_ERR_INTERNAL; + r.text_len = (n >= 0) ? (uint16_t)n : 0; + ipc_send(m->fd, MSG_NOTIFY_QUERY_RESULT, m->req_id, &r, sizeof r); + logmsg("ipc: NOTIFY_QUERY app=%s -> %dB", filter[0] ? filter : "*", n); + break; + } + case MSG_NOTIFY_DISMISS_ALL: { + int rc = meow_notif_clear_active(); + m_ack_t a; + a.code = (rc >= 0) ? MEOW_OK : (uint32_t)rc; + a.status = (uint32_t)(rc > 0 ? rc : 0); /* 清除条数 */ + ipc_send(m->fd, MSG_ACK, m->req_id, &a, sizeof a); + logmsg("ipc: NOTIFY_DISMISS_ALL -> %d cleared", rc); + break; + } case MSG_NOTIFY_LIST: /* ↓ 守护→App 下发通知清单(重同步)。本帧不来自客户端(无请求类型),故不在此应答; * 需用时由守护主动经 meow_notif_serialize_list() + ipc_send 下发(M5 通知中心接入)。 */