/* mp_notif.c — 通知域:优先 launcher IPC 活跃集,回退 TF 归档(查询/撤/清)。 */ #include "mp.h" int notif_csv_field(const char **pp, char *out, int outcap) { const char *p = *pp; int o = 0; if (*p == '"') { p++; while (*p && *p != '"') { if (*p == '"' && p[1] == '"') { if (o < outcap - 1) out[o++] = '"'; p += 2; } else { if (o < outcap - 1) out[o++] = *p; p++; } } if (*p == '"') p++; } else { while (*p && *p != ',' && *p != '\n' && *p != '\r') { if (o < outcap - 1) out[o++] = *p; p++; } } if (o < outcap) { out[o] = 0; } else if (outcap > 0) { out[outcap - 1] = 0; } if (*p == ',') p++; *pp = p; return 0; } int notif_parse_line(const char *line, notif_rec_t *r) { const char *p = line; char f1[32], f2[16], f6[8], f7[16], f11[8], f12[8], f13[8]; char app[40], icon[64], cat[40], sum[96], body[128], tgt[64]; notif_csv_field(&p, f1, sizeof f1); notif_csv_field(&p, f2, sizeof f2); notif_csv_field(&p, app, sizeof app); notif_csv_field(&p, icon, sizeof icon); notif_csv_field(&p, cat, sizeof cat); notif_csv_field(&p, f6, sizeof f6); notif_csv_field(&p, f7, sizeof f7); notif_csv_field(&p, sum, sizeof sum); notif_csv_field(&p, body, sizeof body); notif_csv_field(&p, tgt, sizeof tgt); notif_csv_field(&p, f11, sizeof f11); notif_csv_field(&p, f12, sizeof f12); notif_csv_field(&p, f13, sizeof f13); r->ms = strtoll(f1, NULL, 10); r->id = (unsigned)strtoul(f2, NULL, 10); r->urgency = atoi(f6); r->progress = atoi(f7); r->ongoing = atoi(f11); r->media = atoi(f12); r->dismissed = atoi(f13); snprintf(r->app_id, sizeof r->app_id, "%s", app); snprintf(r->app_icon, sizeof r->app_icon, "%s", icon); snprintf(r->category, sizeof r->category, "%s", cat); snprintf(r->summary, sizeof r->summary, "%s", sum); snprintf(r->body, sizeof r->body, "%s", body); snprintf(r->target, sizeof r->target, "%s", tgt); return 0; } int notif_load_list(notif_rec_t *recs, int maxrec) { FILE *f = fopen(g_notif_log, "r"); if (!f) return -1; int total = 0, head = 0; char line[1400]; notif_rec_t r; while (fgets(line, sizeof line, f)) { if (notif_parse_line(line, &r) != 0) continue; recs[head] = r; head = (head + 1) % maxrec; if (total < maxrec) total++; } fclose(f); if (total < maxrec) return total; /* 未满窗:recs[0..total-1] 即 oldest..newest */ /* 满窗:oldest 在 recs[head],重排为 oldest..newest 连续 */ notif_rec_t tmp[NOTIF_PREVIEW_MAX]; for (int i = 0; i < maxrec; i++) tmp[i] = recs[(head + i) % maxrec]; memcpy(recs, tmp, (size_t)maxrec * sizeof recs[0]); return total; } const char *notif_app_name(const char *app_id) { if (!strcmp(app_id, "system")) return "系统"; return app_id; } void notif_emit_item(sbuf_t *out, const notif_rec_t *r) { sA(out, "{\"id\":%u,\"app_id\":", r->id); sA_str(out, r->app_id); sA(out, ",\"app_name\":"); sA_str(out, notif_app_name(r->app_id)); sA(out, ",\"app_icon\":"); sA_str(out, r->app_icon); sA(out, ",\"category\":"); sA_str(out, r->category); sA(out, ",\"summary\":"); sA_str(out, r->summary); sA(out, ",\"body\":"); sA_str(out, r->body); sA(out, ",\"urgency\":%d,\"ts\":%lld,\"progress\":", r->urgency, r->ms > 0 ? r->ms / 1000 : 0); if (r->progress >= 0) sA(out, "%d", r->progress); else sA(out, "null"); sA(out, ",\"ongoing\":%s,\"media\":%s,\"target_activity\":", r->ongoing ? "true" : "false", r->media ? "true" : "false"); sA_str(out, r->target); sA(out, ",\"dismissed\":%s}", r->dismissed ? "true" : "false"); } int notif_ipc_query(notif_rec_t *recs, int maxrec) { int fd = ipc_connect_tmo(g_sock_launcher, IPC_TIMEOUT_MS); if (fd < 0) return -1; int cnt = -1; if (ipc_hello(fd) < 0) goto done; if (send_frame(fd, MSG_NOTIFY_QUERY, 8, NULL, 0) < 0) goto done; uint8_t buf[MEOW_IPC_MAX + MEOW_IPC_HDR_LEN]; uint16_t type; uint32_t rid; int n = recv_frame(fd, &type, &rid, buf, sizeof buf); if (n < (int)sizeof(m_notify_query_result_t)) goto done; if (type != MSG_NOTIFY_QUERY_RESULT) goto done; m_notify_query_result_t qr; memcpy(&qr, buf, sizeof qr); if (qr.code != MEOW_OK) goto done; uint16_t tl = qr.text_len; if (tl >= sizeof qr.text) tl = (uint16_t)(sizeof qr.text - 1); if (tl == 0) { cnt = 0; goto done; } /* 无活跃通知 */ jv_t *arr = jv_parse(qr.text, tl); if (!arr) goto done; if (arr->type != J_ARR) { jv_free(arr); goto done; } int c = 0; for (jv_t *e = arr->child; e && c < maxrec; e = e->next) { if (e->type != J_OBJ) continue; notif_rec_t r; memset(&r, 0, sizeof r); r.progress = -1; r.urgency = 0; jv_t *v; if ((v = jv_obj_get(e, "id")) && v->type == J_NUM) r.id = (unsigned)v->inum; if ((v = jv_obj_get(e, "app_id")) && v->type == J_STR) snprintf(r.app_id, sizeof r.app_id, "%s", v->str); if ((v = jv_obj_get(e, "app_icon")) && v->type == J_STR) snprintf(r.app_icon, sizeof r.app_icon, "%s", v->str); if ((v = jv_obj_get(e, "category")) && v->type == J_STR) snprintf(r.category, sizeof r.category, "%s", v->str); if ((v = jv_obj_get(e, "summary")) && v->type == J_STR) snprintf(r.summary, sizeof r.summary, "%s", v->str); if ((v = jv_obj_get(e, "body")) && v->type == J_STR) snprintf(r.body, sizeof r.body, "%s", v->str); if ((v = jv_obj_get(e, "target_activity")) && v->type == J_STR) snprintf(r.target, sizeof r.target, "%s", v->str); if ((v = jv_obj_get(e, "urgency")) && v->type == J_NUM) r.urgency = (int)v->inum; /* ts 为 epoch 毫秒 → 存入 r.ms(emit 时 /1000 出秒,与 TF 同口径) */ if ((v = jv_obj_get(e, "ts")) && v->type == J_NUM) r.ms = (long long)v->inum; if ((v = jv_obj_get(e, "progress")) && v->type == J_NUM) r.progress = (int)v->inum; /* <0=无;json 无该键则 -1 */ if ((v = jv_obj_get(e, "ongoing")) && v->type == J_NUM) r.ongoing = (int)v->inum; if ((v = jv_obj_get(e, "media")) && v->type == J_NUM) r.media = (int)v->inum; recs[c++] = r; } jv_free(arr); /* 按 ms 升序(oldest..newest),与 TF 路径一致(emit 时倒序遍历= newest first)。 */ for (int i = 1; i < c; i++) { notif_rec_t key = recs[i]; int j = i - 1; while (j >= 0 && recs[j].ms > key.ms) { recs[j + 1] = recs[j]; j--; } recs[j + 1] = key; } cnt = c; done: close(fd); return cnt; } int notif_ipc_dismiss(unsigned id) { int fd = ipc_connect_tmo(g_sock_launcher, IPC_TIMEOUT_MS); if (fd < 0) return -1; int rc = -1; if (ipc_hello(fd) < 0) goto done; uint32_t nid = (uint32_t)id; if (send_frame(fd, MSG_NOTIFY_DISMISS, 9, &nid, sizeof nid) < 0) goto done; uint8_t buf[64]; uint16_t type; uint32_t rid; int n = recv_frame(fd, &type, &rid, buf, sizeof buf); if (n < (int)sizeof(m_ack_t)) goto done; if (type != MSG_ACK) goto done; m_ack_t a; memcpy(&a, buf, sizeof a); rc = (a.code == MEOW_OK) ? 0 : -1; done: close(fd); return rc; } int notif_ipc_dismiss_all(int *out_count) { int fd = ipc_connect_tmo(g_sock_launcher, IPC_TIMEOUT_MS); if (fd < 0) return -1; int rc = -1; if (ipc_hello(fd) < 0) goto done; if (send_frame(fd, MSG_NOTIFY_DISMISS_ALL, 10, NULL, 0) < 0) goto done; uint8_t buf[64]; uint16_t type; uint32_t rid; int n = recv_frame(fd, &type, &rid, buf, sizeof buf); if (n < (int)sizeof(m_ack_t)) goto done; if (type != MSG_ACK) goto done; m_ack_t a; memcpy(&a, buf, sizeof a); if (a.code == MEOW_OK) { if (out_count) *out_count = (int)a.status; rc = 0; } done: close(fd); return rc; } int api_notifications(int sock) { notif_rec_t recs[NOTIF_PREVIEW_MAX]; const char *source = "ipc"; int n = notif_ipc_query(recs, NOTIF_PREVIEW_MAX); if (n < 0) { /* IPC 不可达 → 回退 TF 归档 */ n = notif_load_list(recs, NOTIF_PREVIEW_MAX); source = (n < 0) ? "none" : "tf"; if (n < 0) n = 0; } sbuf_t arr = {0}; int first = 1; for (int i = n - 1; i >= 0; i--) { /* newest first */ if (!first) sA(&arr, ","); first = 0; notif_emit_item(&arr, &recs[i]); } sbuf_t s = {0}; sA(&s, "{\"count\":%d,\"source\":\"%s\",\"items\":[", n, source); if (arr.n) sA_append(&s, arr.b, arr.n); sA(&s, "],\"notifications\":["); if (arr.n) sA_append(&s, arr.b, arr.n); sA(&s, "]}"); sbuf_free(&arr); int rc = send_json(sock, 200, s.b ? s.b : ""); sbuf_free(&s); return rc; } int notif_rewrite(int mode, unsigned del_id) { if (!g_notif_log || !g_notif_log[0]) return -1; char tmppath[1400]; snprintf(tmppath, sizeof tmppath, "%s.panel.tmp", g_notif_log); FILE *in = fopen(g_notif_log, "r"); if (!in) return 0; FILE *out = fopen(tmppath, "w"); if (!out) { fclose(in); return -1; } int removed = 0; char line[1400]; while (fgets(line, sizeof line, in)) { notif_rec_t r; int parsed = (notif_parse_line(line, &r) == 0); int drop = 0; if (mode == 1) { if (!parsed) drop = 1; else drop = !(r.ongoing || r.media || r.progress >= 0 || !strcmp(r.category, "media")); } else { if (parsed && r.id == del_id) drop = 1; } if (drop) removed++; else fputs(line, out); } fclose(in); if (fclose(out) != 0) return -1; if (rename(tmppath, g_notif_log) < 0) { unlink(tmppath); return -1; } return removed; } int api_notification_delete(int sock, unsigned id) { /* 优先 IPC 联动 launcher 内存(launcher 撤下时自动归档留档);IPC 不可达/未受理回退 TF 重写。 */ if (notif_ipc_dismiss(id) == 0) { char d[64]; snprintf(d, sizeof d, "{\"id\":%u,\"dismissed\":true}", id); return send_json(sock, 200, d); } int removed = notif_rewrite(0, id); if (removed < 0) return send_err(sock, 500, "INTERNAL", "写通知存储失败"); if (removed == 0) return send_err(sock, 404, "NOT_FOUND", "通知不存在"); char d[64]; snprintf(d, sizeof d, "{\"id\":%u,\"dismissed\":true}", id); return send_json(sock, 200, d); } int api_notification_dismiss_all(int sock) { int cleared = 0; if (notif_ipc_dismiss_all(&cleared) == 0) { /* 联动 launcher 内存,返回清除数 */ char d[64]; snprintf(d, sizeof d, "{\"dismissed\":%d}", cleared); return send_json(sock, 200, d); } int removed = notif_rewrite(1, 0); /* IPC 不可达 → 回退重写 TF 日志 */ if (removed < 0) return send_err(sock, 500, "INTERNAL", "写通知存储失败"); char d[64]; snprintf(d, sizeof d, "{\"dismissed\":%d}", removed); return send_json(sock, 200, d); }