/* util.c — MeowOS launcher 拆分模块(LAUNCHER_SPLIT_PLAN 拆分)。*/ /* 本文件由拆分工具从 meow_launcherd.c 按职责搬移;逻辑与原文 0-diff(仅 static 可见性按跨模块需求调整)。*/ #include "meow_launcher_internal.h" #include "meow_ams_start.h" /* MSG_AMS_START pending/拉起决策(meow_ams_start.c):set/set_ex + busy 同步 + ops 组装 */ /* M2.x 应用日志目录简化:一次性迁移 logs/apps//logs → logs/(定义见下,log_open_card 先调用)。 */ static void log_migrate_apps(void); int log_fd = -1; /* file log fd on the TF card */ char log_dir[256] = ""; /* "MeowOS/logs" on card, or "" */ char log_file[256] = ""; /* full path of the boot log file */ /* M2.85 内存护栏:读 /proc/meminfo 的 MemAvailable(kB)。失败返回 -1。 * 供 launcher 在低内存时对大字库请求降档(OOM 防线)。 */ long sys_mem_available_kb(void) { FILE *f = fopen("/proc/meminfo", "r"); if (!f) return -1; char line[256]; long kb = -1; while (fgets(line, sizeof line, f)) { if (sscanf(line, "MemAvailable: %ld kB", &kb) == 1) break; } fclose(f); return kb; } static int card_available(void) { /* TF card mounted? */ FILE *f = fopen(MOUNTS, "r"); if (!f) return 0; char line[256]; int found = 0; while (fgets(line, sizeof line, f)) { if (strstr(line, " /usr/data/mnt/sd_0 ")) { found = 1; break; } } fclose(f); if (!found) return 0; /* writable logs dir? */ mkdir(LOG_DIR_CARD, 0755); return access(LOG_DIR_CARD, W_OK) == 0; } static void boot_timestamp(char *out, size_t n) { /* reuse the timestamp cached for this boot session (/tmp is cleared on * reboot) so repeated launcher starts in one boot share one log file */ int fd = open("/tmp/meow_bootts", O_RDONLY); if (fd >= 0) { int r = read(fd, out, (int)n - 1); close(fd); if (r > 0) { out[r] = 0; return; } } time_t now = time(NULL); time_t up = 0; fd = open("/proc/uptime", O_RDONLY); if (fd >= 0) { char buf[64]; int r = read(fd, buf, sizeof buf - 1); close(fd); if (r > 0) { buf[r] = 0; char *p = strchr(buf, ' '); if (p) *p = 0; up = (time_t)atof(buf); } } time_t boot = (time_t)((double)now - (double)up + 0.5); struct tm tm; localtime_r(&boot, &tm); strftime(out, n, "meow_%Y%m%d_%H%M%S", &tm); /* cache for the rest of this boot session */ fd = open("/tmp/meow_bootts", O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd >= 0) { write(fd, out, strlen(out)); close(fd); } } unsigned long long now_ms(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (unsigned long long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; } /* M2.18 Bug 1: 无符号回绕保护的「空闲时长」。 * launch_app()/confirm_reboot_player() 是阻塞调用,主循环 for(;;) 顶部的 t=now_ms() * 在 app 运行期间被卡住而停在启动前旧值;app 退出后 last_input_ms 被置为 app 退出 * 时刻(较大值)。若该圈 any_input==0(如触摸释放单独落在下一圈、音量/播放边沿发生在 * 更早圈),line「if (any_input) last_input_ms = t」不执行 → 检查处 t < last_input_ms, * (unsigned long long)(t - last_input_ms) 无符号相减回绕成 2^64-Δ 的极大值,恒 >= 30s * → 误判「空闲超时」→ 立即 meow_panel_off()。r3proii 日志铁证:auto screen-off after * 30s idle (t=141578 last=145688),仅 122ms 即被误判为已空闲 30s。凡判定空闲一律经此 * 函数取差:t < last(t 过期)时返回 0,绝不回绕。 */ unsigned long long idle_ms_since(unsigned long long t, unsigned long long last) { return (t >= last) ? (t - last) : 0; } /* remove oldest log files until total size <= limit (called at boot) */ void log_cleanup_old(void) { struct dirent *de; DIR *d = opendir(LOG_DIR_CARD); if (!d) return; /* collect meow_*.log with mtime + size */ struct logfile { char name[64]; long long size; long long mtime; } files[64]; int n = 0; while ((de = readdir(d)) && n < 64) { if (strncmp(de->d_name, "meow_", 5)) continue; char path[320]; snprintf(path, sizeof path, "%s/%s", LOG_DIR_CARD, de->d_name); struct stat st; if (stat(path, &st) || !S_ISREG(st.st_mode)) continue; files[n].mtime = (long long)st.st_mtime; files[n].size = (long long)st.st_size; snprintf(files[n].name, sizeof files[n].name, "%s", de->d_name); n++; } closedir(d); long long total = 0; for (int i = 0; i < n; i++) total += files[i].size; /* simple selection sort by mtime ascending (oldest first) */ for (int i = 0; i < n; i++) for (int j = i + 1; j < n; j++) if (files[j].mtime < files[i].mtime) { struct logfile t = files[i]; files[i] = files[j]; files[j] = t; } int deleted = 0; for (int i = 0; i < n && total > (long long)LOG_SIZE_LIMIT; i++) { char path[320]; snprintf(path, sizeof path, "%s/%s", LOG_DIR_CARD, files[i].name); if (unlink(path) == 0) { total -= files[i].size; deleted++; } } if (deleted) fprintf(stderr, "log_cleanup: deleted %d old log(s), total now %lld\n", deleted, total); } /* try to open the card log file; returns 0 on success */ int log_open_card(void) { if (log_fd >= 0) return 0; if (!card_available()) return -1; log_migrate_apps(); /* M2.x 一次性迁移:logs/apps//logs → logs/(幂等,见下) */ char ts[64]; boot_timestamp(ts, sizeof ts); snprintf(log_dir, sizeof log_dir, "%s", LOG_DIR_CARD); snprintf(log_file, sizeof log_file, "%s/%s.log", LOG_DIR_CARD, ts); int fd = open(log_file, O_WRONLY | O_APPEND | O_CREAT, 0644); if (fd < 0) return -1; log_fd = fd; return 0; } /* M2.x 应用日志目录简化:mkdir -p(幂等,已存在 EEXIST 视为成功)。返回 0=成功 -1=失败。 * 供 launcher 为每 App 建新布局日志目录 logs// 用(沿用 SDK 同款逐分量 mkdir)。 */ int meow_mkdir_p(const char *path) { char tmp[256]; size_t len, i; if (!path || !path[0] || strlen(path) >= sizeof tmp) return -1; len = strlen(path); memcpy(tmp, path, len + 1); if (tmp[len - 1] == '/') tmp[len - 1] = '\0'; /* 去尾斜杠 */ for (i = 1; i <= strlen(tmp); i++) { if (tmp[i] == '/' || tmp[i] == '\0') { char ch = tmp[i]; tmp[i] = '\0'; if (tmp[0] && mkdir(tmp, 0755) != 0 && errno != EEXIST) return -1; tmp[i] = ch; } } return 0; } /* M2.x 一次性迁移(幂等,代码可后续移除):旧布局 LOG_DIR_CARD/apps//logs/.log * 并入新布局 LOG_DIR_CARD//.log,然后删除旧 apps/ 整树。卡可用时随 log_open_card * 跑一次;apps/ 已不存在则直接返回。同卡同 fs,文件用 rename() 移动(目标同名被覆盖——迁移 * 内只挪旧文件,不写新日志)。总容量上限纪律不变:应用日志仍由 SDK 按 App 50MB 清理,boot 日志 * 仍走 log_cleanup_old()。 */ static void log_rm_rf(const char *path) { struct stat st; if (stat(path, &st) != 0) return; if (S_ISDIR(st.st_mode)) { DIR *d = opendir(path); if (d) { struct dirent *e; while ((e = readdir(d)) != NULL) { if (e->d_name[0] == '.' && (e->d_name[1] == '\0' || (e->d_name[1] == '.' && e->d_name[2] == '\0'))) continue; char sub[512]; snprintf(sub, sizeof sub, "%s/%s", path, e->d_name); log_rm_rf(sub); } closedir(d); } rmdir(path); } else { unlink(path); } } static void log_migrate_apps(void) { char old_root[512], old_dir[512], old_logs[512], new_dir[512]; DIR *d, *dl, *dd; struct dirent *e; snprintf(old_root, sizeof old_root, "%s/apps", LOG_DIR_CARD); d = opendir(old_root); if (!d) return; /* 旧路径不存在,无需迁移 */ while ((e = readdir(d)) != NULL) { struct stat st; if (e->d_name[0] == '.') continue; snprintf(old_dir, sizeof old_dir, "%s/%s", old_root, e->d_name); if (stat(old_dir, &st) != 0 || !S_ISDIR(st.st_mode)) continue; snprintf(new_dir, sizeof new_dir, "%s/%s", LOG_DIR_CARD, e->d_name); if (meow_mkdir_p(new_dir) != 0) continue; /* 新目录建不了,跳过该 App */ /* 旧布局 apps//logs/.log → logs//.log */ snprintf(old_logs, sizeof old_logs, "%s/logs", old_dir); dl = opendir(old_logs); if (dl) { struct dirent *el; while ((el = readdir(dl)) != NULL) { struct stat stl; char src[512], dst[512]; if (el->d_name[0] == '.') continue; snprintf(src, sizeof src, "%s/%s", old_logs, el->d_name); if (stat(src, &stl) != 0 || !S_ISREG(stl.st_mode)) continue; snprintf(dst, sizeof dst, "%s/%s", new_dir, el->d_name); rename(src, dst); } closedir(dl); } /* 早期设计若直接把 .log 散在 apps// 下,一并并入(跳过已处理的 logs/ 子目录) */ dd = opendir(old_dir); if (dd) { struct dirent *ed; while ((ed = readdir(dd)) != NULL) { struct stat stf; char src[512], dst[512]; if (ed->d_name[0] == '.') continue; if (strcmp(ed->d_name, "logs") == 0) continue; snprintf(src, sizeof src, "%s/%s", old_dir, ed->d_name); if (stat(src, &stf) != 0 || !S_ISREG(stf.st_mode)) continue; snprintf(dst, sizeof dst, "%s/%s", new_dir, ed->d_name); rename(src, dst); } closedir(dd); } log_rm_rf(old_dir); } closedir(d); rmdir(old_root); /* apps/ 清空则删;非空则忽略 */ fprintf(stderr, "log_migrate: logs/apps/* -> logs/ done\n"); } /* flush the /tmp boot buffer into the file log (once the card mounts) */ void log_poll_card(void) { if (log_fd >= 0) return; if (log_open_card() != 0) return; /* move the /tmp boot buffer into the card log, then drop it */ int fd = open(BOOT_BUF_TMP, O_RDONLY); if (fd >= 0) { char chunk[1024]; ssize_t n; while ((n = read(fd, chunk, sizeof chunk)) > 0) { if (write(log_fd, chunk, n) < 0) { close(fd); log_fd = -1; return; } } close(fd); unlink(BOOT_BUF_TMP); logmsg("boot buffer moved into %s", log_file); } else { logmsg("card log open: %s", log_file); } } /* bounded append to the /tmp boot buffer (no card log open yet) */ static void boot_buf_put(const char *line, int len) { struct stat st; if (stat(BOOT_BUF_TMP, &st) == 0 && st.st_size > BOOT_BUF_MAX) unlink(BOOT_BUF_TMP); /* roll the buffer (boot-stage only) */ int fd = open(BOOT_BUF_TMP, O_WRONLY | O_APPEND | O_CREAT, 0644); if (fd < 0) return; if (write(fd, line, len) < 0) { /* best effort */ } close(fd); } void logmsg(const char *fmt, ...) { char buf[512]; va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof buf, fmt, ap); va_end(ap); char line[576]; int l = snprintf(line, sizeof line, "%llu %s\n", now_ms(), buf); if (log_fd >= 0) { if (write(log_fd, line, l) < 0) log_fd = -1; /* card gone -> buffer */ } if (log_fd < 0) boot_buf_put(line, l); fprintf(stderr, "%llu %s\n", now_ms(), buf); } int screen_off_timeout = SCREEN_OFF_TIMEOUT_DEFAULT; int screen_off_mode = SCREEN_OFF_MODE_DEFAULT; int power_longpress_menu_ms = PWR_MENU_MS_DEFAULT; int power_forceoff_ms = PWR_FORCE_MS_DEFAULT; int refresh_rate_cfg = REFRESH_RATE_CFG_DEFAULT; /* 目标档 60/30/15(上限,default 60) */ int refresh_always_max = 0; /* 1=恒 max 不降档 */ static void meow_dirty_reset(void); void put_px(int x, int y, uint32_t color) { if (x < 0 || x >= fb_xres || y < 0 || y >= fb_yres) return; unsigned char *p = fb + y * fb_line + x * (fb_bpp / 8); if (fb_bpp == 16) { uint16_t c = ((color >> 8) & 0xF800) | ((color >> 5) & 0x07E0) | ((color >> 3) & 0x001F); *(uint16_t *)p = c; } else { *(uint32_t *)p = (color << 8) | 0xFF; } } void fill_rect(int x0, int y0, int w, int h, uint32_t color) { for (int y = y0; y < y0 + h; y++) for (int x = x0; x < x0 + w; x++) put_px(x, y, color); } /* 5x7 font (same table as hello.c) */ static const unsigned char font[][8] = { {0x0E,0x11,0x13,0x15,0x19,0x11,0x0E,0x00}, {0x04,0x0C,0x04,0x04,0x04,0x04,0x0E,0x00}, {0x0E,0x11,0x01,0x02,0x04,0x08,0x1F,0x00}, {0x1F,0x02,0x04,0x02,0x01,0x11,0x0E,0x00}, {0x02,0x06,0x0A,0x12,0x1F,0x02,0x02,0x00}, {0x1F,0x10,0x1E,0x01,0x01,0x11,0x0E,0x00}, {0x06,0x08,0x10,0x1E,0x11,0x11,0x0E,0x00}, {0x1F,0x01,0x02,0x04,0x08,0x08,0x08,0x00}, {0x0E,0x11,0x11,0x0E,0x11,0x11,0x0E,0x00}, {0x0E,0x11,0x11,0x0F,0x01,0x02,0x0C,0x00}, {0x0E,0x11,0x11,0x1F,0x11,0x11,0x11,0x00}, {0x1E,0x11,0x11,0x1E,0x11,0x11,0x1E,0x00}, {0x0E,0x11,0x10,0x10,0x10,0x11,0x0E,0x00}, {0x1C,0x12,0x11,0x11,0x11,0x12,0x1C,0x00}, {0x1F,0x10,0x10,0x1E,0x10,0x10,0x1F,0x00}, {0x1F,0x10,0x10,0x1E,0x10,0x10,0x10,0x00}, {0x0E,0x11,0x10,0x17,0x11,0x11,0x0F,0x00}, {0x11,0x11,0x11,0x1F,0x11,0x11,0x11,0x00}, {0x0E,0x04,0x04,0x04,0x04,0x04,0x0E,0x00}, {0x07,0x02,0x02,0x02,0x02,0x12,0x0C,0x00}, {0x11,0x12,0x14,0x18,0x14,0x12,0x11,0x00}, {0x10,0x10,0x10,0x10,0x10,0x10,0x1F,0x00}, {0x11,0x1B,0x15,0x15,0x11,0x11,0x11,0x00}, {0x11,0x19,0x15,0x13,0x11,0x11,0x11,0x00}, {0x0E,0x11,0x11,0x11,0x11,0x11,0x0E,0x00}, {0x1E,0x11,0x11,0x1E,0x10,0x10,0x10,0x00}, {0x0E,0x11,0x11,0x11,0x15,0x12,0x0D,0x00}, {0x1E,0x11,0x11,0x1E,0x14,0x12,0x11,0x00}, {0x0F,0x10,0x10,0x0E,0x01,0x01,0x1E,0x00}, {0x1F,0x04,0x04,0x04,0x04,0x04,0x04,0x00}, {0x11,0x11,0x11,0x11,0x11,0x11,0x0E,0x00}, {0x11,0x11,0x11,0x11,0x11,0x0A,0x04,0x00}, {0x11,0x11,0x11,0x15,0x15,0x1B,0x11,0x00}, {0x11,0x11,0x0A,0x04,0x0A,0x11,0x11,0x00}, {0x11,0x11,0x11,0x0A,0x04,0x04,0x04,0x00}, {0x1F,0x01,0x02,0x04,0x08,0x10,0x1F,0x00}, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, {0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x00}, {0x04,0x04,0x04,0x04,0x04,0x00,0x04,0x00}, {0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x00}, {0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x00}, }; static int glyph_index(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'A' && c <= 'Z') return 10 + (c - 'A'); if (c >= 'a' && c <= 'z') return 10 + (c - 'a'); if (c == ' ') return 36; if (c == '.') return 37; if (c == '!') return 38; if (c == '-' || c == '_') return 39; return 36; } static void draw_glyph(int x, int y, char c, int scale, uint32_t color) { const unsigned char *g = font[glyph_index(c)]; for (int row = 0; row < 7; row++) { unsigned char bits = g[row]; for (int col = 0; col < 5; col++) { if (bits & (0x10 >> col)) { for (int dy = 0; dy < scale; dy++) for (int dx = 0; dx < scale; dx++) put_px(x + col * scale + dx, y + row * scale + dy, color); } } } } void draw_text(int x, int y, const char *s, int scale, uint32_t color) { while (*s) { draw_glyph(x, y, *s, scale, color); x += 6 * scale; s++; } } /* 24-bit RGB → RGB565(与 put_px/meow_font 一致) */ uint16_t rgb565(uint32_t c) { return (uint16_t)(((c >> 8) & 0xF800) | ((c >> 5) & 0x07E0) | ((c >> 3) & 0x001F)); } /* 统一文本绘制:字库就绪用 meow_font(真字体/中文),否则回退 5x7 点阵。 * size 为字库字号(28=状态栏/大标题,20=正文/按钮);回退时按 scale 近似。 */ void ui_draw(int x, int y, const char *s, uint32_t color, int size) { if (mfont_ready) { meow_font_drawtext((uint16_t *)fb, x, y, s, rgb565(color), size); } else { draw_text(x, y, s, (size >= 16) ? 2 : 1, color); } } /* 文本宽度(字库 or 回退估算),用于右对齐/居中 */ int ui_width(const char *s, int size) { if (mfont_ready) return meow_font_width_text(s, size); return (int)strlen(s) * 6 * ((size >= 16) ? 2 : 1); } /* 文本高度(字库行高 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); } /* ================================================================== */ /* A-1/A-2 渲染桥:launcher 手动脏模型 → SDK 脏追踪,呈现走 meow_ui_flush() */ /* launcher 的绘制(put_px/fill_rect/meow_font/dim_screen)直接把像素画进 */ /* SDK 的 back_buf(fb),在此仅把「哪些区域变了」桥进 SDK 的脏追踪; */ /* present 不再由 launcher 做,统一 meow_ui_flush() = SDK present(内含 */ /* vsync + 帧率门控 + 原子 blit 脏区)。launcher 不再自带 meow_present/ */ /* refresh_governor —— 帧率调度已归口 SDK(PERF_SCHEDULER 显示子域)。 */ /* ================================================================== */ /* 标记全屏脏:桥进 SDK 的整帧脏,下次 meow_ui_flush() 整帧 blit。 */ void meow_dirty_full(void) { meow_gfx_mark_full(); } /* 清空脏集:SDK present 上屏后自清;launcher 侧无需再清(保留接口兼容)。 * (拆分后无调用点,保留钩子;unused 满足 -Wall 0 warning) */ static void __attribute__((unused)) meow_dirty_reset(void) { } /* 标记一个矩形脏:桥进 SDK 脏追踪(相交合并/超限转全屏由 SDK dirty_add 处理)。 */ void meow_dirty_add(int x, int y, int w, int h) { meow_gfx_mark_dirty(x, y, w, h); } /* 标识符字符(含 '.',用于防止 json_str 误命中更长键的一部分) */ static int ident_ch(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '.'; } /* 把字符串末尾不完整的 UTF-8 序列砍掉(截断到最后一个完整码点边界)。 * 根因记录:显示名按字节截断若落在多字节序列中间,下游(面板 JSON/绘制) * 会拿到非法 UTF-8(amsd「流媒体客户端」事故 2026-09-02)。 */ static void utf8_trim_incomplete(char *s) { size_t n = strlen(s), i = 0, last_good = 0; while (i < n) { unsigned char c = (unsigned char)s[i]; int len = (c < 0x80) ? 1 : (c >= 0xF0) ? 4 : (c >= 0xE0) ? 3 : (c >= 0xC0) ? 2 : 1; if (i + (size_t)len > n) break; i += (size_t)len; last_good = i; } s[last_good] = '\0'; } /* 极简 JSON/扁平值提取:在一行里找 key,读取其值(跳过 ':'/'=' 与可选引号)。 * 支持 JSON `"name": "Hello"` 与扁平 `framework=true`。返回拷贝到 out 的字符数。 * 返回 0=未命中/空。截断做 UTF-8 整序列保护。 */ int json_str(const char *buf, const char *key, char *out, int olen) { out[0] = 0; if (!buf || !key || olen <= 0) return 0; const char *p = strstr(buf, key); if (!p) return 0; if (p > buf && ident_ch((unsigned char)p[-1])) return 0; /* 键前缀需独立 */ p += strlen(key); if (*p == '"') p++; /* JSON 键自身右引号:跳到 ':'/'=' */ while (*p && (*p == ':' || *p == '=')) p++; while (*p == ' ' || *p == '\t') p++; if (*p == '"') p++; /* 值左引号(JSON 字符串值) */ int i = 0; while (*p && *p != '"' && *p != ',' && *p != '\n' && *p != '\r' && i < olen - 1) out[i++] = *p++; out[i] = 0; while (i > 0 && (out[i - 1] == ' ' || out[i - 1] == '\t')) out[--i] = 0; utf8_trim_incomplete(out); return (int)strlen(out); } /* state: 0 ok / 1 no storage card / 2 no usable apps */ int card_present(void) { FILE *f = fopen(MOUNTS, "r"); if (!f) return 0; char line[256]; int found = 0; while (fgets(line, sizeof line, f)) { if (strstr(line, " /usr/data/mnt/sd_0 ")) { found = 1; break; } } fclose(f); return found; } /* ================================================================== */ /* MeowOS 系统拔营(M2.2 批次):TF 卡挂载 + WiFi 联网 */ /* 官方环境由 hiby_player / sys_server 负责这两件事;MeowOS 作为主系统 */ /* 时无播放器,必须由 launcher 主动接管。失败不阻塞主界面,后台周期重试。 */ /* ================================================================== */ int sd_mounted(void) { return card_present(); } /* TF 卡设备节点是否出现(任一常见节点即视为在卡) */ int sd_device_present(void) { return access("/dev/mmcblk0", F_OK) == 0 || access("/dev/mmcblk0p1", F_OK) == 0; } /* 主动挂载 TF 卡到 /usr/data/mnt/sd_0。返回 1=已挂载,0=尚未(调用方轮询重试)。 * 依次尝试 /dev/mmcblk0、/dev/mmcblk0p1,先 -t exfat 再自动探测;卡未插入返回 0。 */ int ensure_card_mounted(void) { if (sd_mounted()) return 1; if (!sd_device_present()) return 0; const char *nodes[2] = { "/dev/mmcblk0", "/dev/mmcblk0p1" }; for (int i = 0; i < 2; i++) { if (access(nodes[i], F_OK) != 0) continue; char cmd[160]; snprintf(cmd, sizeof cmd, "mkdir -p /usr/data/mnt/sd_0; " "PATH=/bin:/sbin:/usr/bin:/usr/sbin mount -t exfat %s /usr/data/mnt/sd_0 2>/dev/null", nodes[i]); if (system(cmd) == 0 && sd_mounted()) { logmsg("sd_0 mounted: %s", nodes[i]); return 1; } snprintf(cmd, sizeof cmd, "mkdir -p /usr/data/mnt/sd_0; " "PATH=/bin:/sbin:/usr/bin:/usr/sbin mount %s /usr/data/mnt/sd_0 2>/dev/null", nodes[i]); if (system(cmd) == 0 && sd_mounted()) { logmsg("sd_0 mounted (auto): %s", nodes[i]); return 1; } } return 0; } int read_file_int(const char *path) { int fd = open(path, O_RDONLY); if (fd < 0) return -1; char buf[16]; int n = read(fd, buf, sizeof buf - 1); close(fd); if (n <= 0) return -1; buf[n] = 0; return atoi(buf); } /* ---- 通知 dump 回调(通知#6 转储 TF 卡;做成真)---- * meow_notif 在 TTL 到点(meow_notif_tick,§8 10s)调用此回调归档一条通知,随后释放槽。 * 本回调把 meow_notif 结构体转成一行 CSV 追加写入 <卡>/MeowOS/notifications/notifications.log * (含 mkdir -p 与按总大小滚动截断、保留最新 10MB),落盘实现在 meow_notif.c 的 * meow_notif_dump_tf()。无卡/不可写仅打点降级(NOTIFICATION_SYSTEM.md §5/§8 + MEWSPEC §5.3)。 */ static void notif_dump_tf_placeholder(const meow_notif_t *n) { meow_notif_dump_tf(n); } /* ---- 通知图标回退回调(用户定案:icon 为空 → 用该 app 默认图标)---- * post 的 app_icon 为空时调用(meow_notif.c 的 notif_resolve_icon),按 app_id 在 app * 注册表(scan_apps 静态 app.json 的 icon)里找该 app 的图标(name/path 或空)。 */ static int notif_app_icon_fallback(const char *app_id, char *out, size_t outsz) { if (!app_id || !app_id[0] || !out || outsz == 0) return 0; struct meow_acct *a = meow_acct_find(app_id); if (a && a->icon[0]) { snprintf(out, outsz, "%s", a->icon); return 1; } return 0; /* 无注册/无图标 → 保持空(通知中心 UI 再回退) */ } void settings_load(void) { meow_settings_init(); /* 幂等:注册默认 + 读 settings.conf */ meow_notif_init(); /* M2a:通知存储初始化(幂等,读 notify.* 配额) */ meow_notif_set_dump_cb(notif_dump_tf_placeholder); /* 通知#6:挂 dump 回调 = TF 转储(meow_notif_dump_tf:一行 CSV 追加 + 10MB 滚动截断) */ meow_notif_set_icon_cb(notif_app_icon_fallback); /* icon 空 → 按 app_id 回退该 app 默认图标(app.json icon) */ if (!settings_cache_registered) { meow_settings_add_listener(settings_sync_cache_listener); settings_cache_registered = 1; } settings_sync_cache_from_backend(); /* M2.86 开机亮度竞态:launcher S70 早启动 /usr/data 未就绪 → 首次 init 读到的是纯默认值 * (display.brightness=101→clamp 100),apply_user_brightness 已挂起 meow_brightness_pending。 * 此处 settings 一旦真正从盘上读到(meow_settings_loaded()==1)即补应用真实用户亮度。 * 幂等:正常路径(已读到、无挂起)恒为 no-op;仅「未读到→已读到」转换时触发一次。 */ if (meow_settings_loaded() && meow_brightness_pending) { apply_user_brightness(); logmsg("deferred user brightness applied after settings ready"); } logmsg("settings: vol=%d(max=%d,step=%d) refresh=%d(always_max=%d) off=%ds mode=%s", settings_volume, settings_volume_max, settings_volume_step, refresh_rate_cfg, refresh_always_max, screen_off_timeout, screen_off_mode ? "black" : "panel"); } meow_sys_state g_sys; char g_meowos_version[16] = MEOWOS_VERSION_DEF; static void load_meowos_version(void) { FILE *f = fopen(MEOWOS_VERSION_FILE, "r"); if (!f) { logmsg("meowos_version: %s missing, use default %s", MEOWOS_VERSION_FILE, g_meowos_version); return; } char buf[32]; if (fgets(buf, sizeof buf, f)) { char *e = buf + strlen(buf); while (e > buf && (e[-1] == '\n' || e[-1] == '\r' || e[-1] == ' ')) e--; *e = 0; if (buf[0]) snprintf(g_meowos_version, sizeof g_meowos_version, "%s", buf); } fclose(f); logmsg("meowos_version: read %s = '%s'", MEOWOS_VERSION_FILE, g_meowos_version); } /* 读一份 sysfs 字符串(剥尾随换行);失败回退 "" */ static void read_sys_str(const char *path, char *buf, size_t n) { buf[0] = 0; int fd = open(path, O_RDONLY); if (fd < 0) return; ssize_t r = read(fd, buf, n - 1); close(fd); if (r <= 0) { buf[0] = 0; return; } buf[r] = 0; while (r > 0 && (buf[r-1] == '\n' || buf[r-1] == '\r')) buf[--r] = 0; } /* 蓝牙实际上电状态(A/E:不只看 hci0 存在——节点常在但控制器可能 DOWN)。 * 本设备 /sys/class/bluetooth/hci0/powered 不存在(旧内核/BlueZ 5.54), * 权威来源为 hciconfig -a hci0 的 UP/DOWN 行。费用较高(spawn),故缓存, * 由 meow_sys_refresh()(进系统一次)/meow_sys_light()(轻刷)节流更新; * 状态栏只读 g_sys.bt_on。 */ int hci_powered(void) { FILE *p = popen("hciconfig -a hci0 2>/dev/null", "r"); if (!p) return 0; int up = 0; char buf[256]; while (fgets(buf, sizeof buf, p)) { /* 状态行含 "UP RUNNING" 或 "DOWN"。匹配单词 UP(避免误中 UART 等)。 */ char *s = buf, *tok; while ((tok = strsep(&s, " \t\r\n")) != NULL) { if (!strcmp(tok, "UP")) { up = 1; break; } } } pclose(p); return up; } /* 读 /proc/net/wireless 的 wlan0 level(dBm,如 -46);失败(文件不存在/无 wlan0 行/解析失败)返回 * WIFI_LVL_UNKNOWN。行格式(Linux wext)形如 " wlan0: 0000 64. -46. -256 0 ..."; * 接口名后第 3 个空白分隔数值 = level(注意末尾带 '.',需剥离)。状态栏 WiFi 信号分档喂给 SDK。 */ static int wifi_signal_level(void) { FILE *f = fopen("/proc/net/wireless", "r"); if (!f) return WIFI_LVL_UNKNOWN; char line[256]; int lvl = WIFI_LVL_UNKNOWN; while (fgets(line, sizeof line, f)) { char *p = strstr(line, "wlan0:"); if (!p) continue; p += 6; /* 跳过 "wlan0:" */ int tok = 0; while (*p) { while (*p == ' ' || *p == '\t') p++; if (!*p) break; char *t = p; while (*p && *p != ' ' && *p != '\t' && *p != '\n') p++; char save = *p; *p = 0; /* 临时截断 token */ if (tok == 2) { /* 第 3 个数值(0 起) = level */ size_t n = strlen(t); if (n && t[n - 1] == '.') t[n - 1] = 0; /* 去尾部 '.'(-46. → -46) */ lvl = atoi(t); *p = save; break; } *p = save; tok++; } break; /* 只需 wlan0 行 */ } fclose(f); return lvl; } /* 充电态权威判定(真机实测 2026-09-01:battery/status 恒 Discharging 撒谎): * 1) mp2731-charger/status(Charging/Full=充电中)为权威; * 2) 不可读回退 fusb302-typec-source/online==1(USB-C PD 控制器插线检测); * 3) 再回退旧逻辑 battery/status==Charging || ac/online==1。 * 返回 1=充电 0=未充电。 */ static int batt_is_charging(void) { char st[16]; read_sys_str("/sys/class/power_supply/mp2731-charger/status", st, sizeof st); if (st[0]) return (strstr(st, "Charg") || strstr(st, "Full")) ? 1 : 0; int onl = read_file_int("/sys/class/power_supply/fusb302-typec-source/online"); if (onl >= 0) return (onl == 1) ? 1 : 0; read_sys_str("/sys/class/power_supply/battery/status", st, sizeof st); if (strstr(st, "Charg") || strstr(st, "Full")) return 1; return read_file_int("/sys/class/power_supply/ac/online") == 1 ? 1 : 0; } /* 全量重抓(进 MeowOS 时调用一次;阻塞可接受)。绝不放进 10ms 主循环。 */ void meow_sys_refresh(void) { load_meowos_version(); /* M2.47:读 /etc/meowos_version(单一数据源,每次刷新对齐) */ int b = read_file_int("/sys/class/power_supply/battery/capacity"); g_sys.batt = (b >= 0 && b <= 100) ? b : -1; g_sys.charging = batt_is_charging(); g_sys.batt_mv = read_file_int("/sys/class/power_supply/battery/voltage_now") / 1000; /* WiFi:wifi_on=网卡管理态 up(射频开);wifi_connected=operstate up(已关联 AP)。 * M2.48:二者分工,状态栏才能区分「开未连接(WIFI_NO)」与「已连接(信号分档)」。 */ char op[16]; read_sys_str("/sys/class/net/wlan0/operstate", op, sizeof op); g_sys.wifi_connected = !strcmp(op, "up") ? 1 : 0; g_sys.wifi_on = wlan_radio_up(); g_sys.wifi_level = g_sys.wifi_connected ? wifi_signal_level() : WIFI_LVL_UNKNOWN; g_sys.wifi_ip[0] = 0; g_sys.wifi_ssid[0] = 0; if (g_sys.wifi_connected) { /* 行形如 "2: wlan0 inet /24 brd ...",取 inet 后第一个斜杠前的 token。 * 用大缓冲读整行,再从局部 token 拷贝到 16B 小字段(避免 OOB)。 */ char big[256]; FILE *p = popen("ip -o addr show wlan0 2>/dev/null | grep 'inet '", "r"); if (p) { if (fgets(big, sizeof big, p)) { char *s = strstr(big, "inet "); if (s) { s += 5; while (*s == ' ') s++; char *e = strchr(s, '/'); if (e) *e = 0; snprintf(g_sys.wifi_ip, sizeof g_sys.wifi_ip, "%s", s); } } pclose(p); } /* SSID 尽力读(wpa_cli 若在;失败留空不外溢)。注意:wpa_cli 可能因 wpa_supplicant * 未跑而挂起——用后台 + 短等待避免阻塞 UI;超时即放弃(SSID 仅面板用,状态栏不读)。 */ } /* 蓝牙:hci0 存在性 + 上电状态 + M2.48 角色/连接(进系统全量重探一次) */ g_sys.bt_seen = access("/sys/class/bluetooth/hci0", F_OK) == 0; g_sys.bt_on = g_sys.bt_seen ? hci_powered() : 0; bt_role_refresh(1); /* 立即探测角色/连接态,供状态栏与设置页"蓝牙模式"行 */ /* 音量(settings;进 MeowOS 全量时把真实 DAC 同步到当前档)/ 亮度 */ g_sys.vol = settings_volume_get(); meow_dac_vol_write(settings_volume); /* M2.13:进 MeowOS 即把 CS43198 DAC 置为当前音量 */ int br = read_brightness(); g_sys.brightness = (br >= 0) ? br : 0; /* 存储:/data 剩余 + TF 卡挂载容量 */ g_sys.data_free_kb = 0; FILE *df = popen("df -k /data 2>/dev/null | awk 'NR==2{print $4}'", "r"); if (df) { char x[24]; if (fgets(x, sizeof x, df)) g_sys.data_free_kb = atol(x); pclose(df); } g_sys.sd_mounted = sd_mounted(); g_sys.sd_free_kb = g_sys.sd_total_kb = 0; if (g_sys.sd_mounted) { FILE *df2 = popen("df -k /usr/data/mnt/sd_0 2>/dev/null | awk 'NR==2{print $4, $2}'", "r"); if (df2) { char x[48]; if (fgets(x, sizeof x, df2)) { sscanf(x, "%ld %ld", &g_sys.sd_free_kb, &g_sys.sd_total_kb); } pclose(df2); } } snprintf(g_sys.ver, sizeof g_sys.ver, "%s", g_meowos_version); logmsg("sys snapshot: batt=%d%% chg=%d wifi=%d ssid=%s ip=%s bt=%d/%d vol=%d bri=%d " "data_free=%ldKB sd=%d(%ld/%ldKB)", g_sys.batt, g_sys.charging, g_sys.wifi_on, g_sys.wifi_ssid[0]?g_sys.wifi_ssid:"-", g_sys.wifi_ip[0]?g_sys.wifi_ip:"-", g_sys.bt_on, g_sys.bt_seen, g_sys.vol, g_sys.brightness, g_sys.data_free_kb, g_sys.sd_mounted, g_sys.sd_free_kb, g_sys.sd_total_kb); } /* 轻刷(1s 定时,只更新时钟/电量/WiFi 信号 + 节流探测蓝牙角色——便宜,绝不碰负载/温度) */ void meow_sys_light(void) { int b = read_file_int("/sys/class/power_supply/battery/capacity"); if (b >= 0 && b <= 100) g_sys.batt = b; g_sys.charging = batt_is_charging(); /* 插拔实时跟踪(两个小 sysfs 节点,开销可忽略) */ char op[16]; read_sys_str("/sys/class/net/wlan0/operstate", op, sizeof op); g_sys.wifi_connected = !strcmp(op, "up") ? 1 : 0; g_sys.wifi_on = wlan_radio_up(); g_sys.wifi_level = g_sys.wifi_connected ? wifi_signal_level() : WIFI_LVL_UNKNOWN; g_sys.vol = settings_volume_get(); /* 每进 MeowOS 重抓;此处同步内存值 */ bt_role_refresh(0); /* M2.48:节流探测蓝牙角色(约 5s 一次,不阻塞主循环) */ } /* M2.x 锁屏:电源菜单扩为 6 钮(2 列×3 行),列坐标 0/1/0/1/0/1;取消(PM_NUM-1)落右下。 */ const int pm_cx[PM_NUM] = { PM_CARD_CX - PM_GAPX / 2, PM_CARD_CX + PM_GAPX / 2, PM_CARD_CX - PM_GAPX / 2, PM_CARD_CX + PM_GAPX / 2, PM_CARD_CX - PM_GAPX / 2, PM_CARD_CX + PM_GAPX / 2 }; static struct meow_ipc_msg ipc_ring[MEOW_IPC_RING]; static volatile uint32_t ipc_head = 0; /* 消费端(主循环)推进 */ static volatile uint32_t ipc_tail = 0; /* 生产端(IPC线程)推进 */ static void ipc_dispatch(struct meow_ipc_msg *m); /* 定义在下方,主循环排空用 */ static int ipc_event_fd = -1; volatile int ipc_shutdown = 0; static volatile int ipc_thread_ok = 0; /* bind+listen 成功 */ char ipc_sock_path[MEOW_IPC_SOCK_LEN] = MEOW_IPC_PATH; static pthread_t ipc_pthread; static struct meow_ipc_conn ipc_conns[MEOW_IPC_CONNS]; /* M4:连接存活跟踪(ipc_thread 单写者维护计数;主循环 meow_ipc_conn_alive 读)。 * 竞态最坏=误判一帧(best-effort 熔断广播可接受;ipc_conns 前 ipc_conn_count 项恒为 * 最新存活集,因关闭时shift压实)。 */ static volatile int ipc_conn_count = 0; /* M4:查 fd 是否为当前存活 IPC 连接(熔断广播 MSG_SYS 投递前判定,防发到已断连接)。 */ int meow_ipc_conn_alive(int fd) { int n = ipc_conn_count; if (fd < 0) return 0; if (n > MEOW_IPC_CONNS) n = MEOW_IPC_CONNS; for (int i = 0; i < n; i++) if (ipc_conns[i].fd == fd) return 1; return 0; } /* §10.5:待处理的 App 前台「展开通知中心」请求(ipc_dispatch 单写者置位;launch_app waitpid * 循环 meow_notif_open_take 读取并清除)。仅当 g_app_session_active(App 前台会话)期间才置位, * 否则回 ERR(无 App 前台,不展开)。 */ static m_notif_open_t g_notif_open; static volatile int g_notif_open_pending = 0; int meow_notif_open_take(m_notif_open_t *out) { if (!out) return 0; if (!g_notif_open_pending) return 0; *out = g_notif_open; g_notif_open_pending = 0; return 1; } /* 会话边界:进入新 App 前台会话前清残留,防旧会话未消费的 OPEN 被新会话误展开。 */ void meow_notif_open_clear(void) { g_notif_open_pending = 0; memset(&g_notif_open, 0, sizeof g_notif_open); } /* 写一帧(16B 头 + 负载)。返回写入字节;<=0 视为失败。 */ static size_t ipc_send(int fd, uint16_t type, uint32_t req_id, const void *payload, uint32_t len) { if (fd < 0) return 0; 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); size_t off = 0; while (off < sizeof hdr) { ssize_t w = write(fd, hdr + off, sizeof hdr - 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; } /* 公开的 ipc_send 包装(media 模块经 ops 注入用;签名与 meow_media_ops.send 一致)。 */ size_t meow_ipc_send_pub(int fd, uint16_t type, uint32_t req_id, const void *payload, uint32_t len) { return ipc_send(fd, type, req_id, payload, len); } /* 组装完的帧入队(SPSC 无锁,先写槽再推进 tail,__sync 发布)。 */ static void ipc_enqueue_frame(struct meow_ipc_conn *c) { uint16_t type; uint32_t req_id, len; memcpy(&type, c->hdr + 6, 2); memcpy(&req_id, c->hdr + 8, 4); len = c->pay_tot; if ((ipc_tail - ipc_head) >= MEOW_IPC_RING) { logmsg("ipc: ring full, drop frame type=0x%04x", type); return; } uint32_t slot = ipc_tail % MEOW_IPC_RING; struct meow_ipc_msg *m = &ipc_ring[slot]; m->type = type; m->req_id = req_id; m->fd = c->fd; uint32_t copy = len < MEOW_IPC_CARGO ? len : MEOW_IPC_CARGO; m->plen = (uint16_t)copy; memcpy(m->payload, c->pay, copy); __sync_synchronize(); ipc_tail++; if (ipc_event_fd >= 0) { uint64_t one = 1; (void)write(ipc_event_fd, &one, sizeof one); } } /* 读一个连接的可用字节并组装成帧(>=1 完整帧即入队)。返回 0=ok(可能已入队), * -1=连接关闭/坏帧(调用方应关闭并回收)。 */ static int ipc_conn_read(struct meow_ipc_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) { logmsg("ipc: bad magic %08x", magic); return -1; } if (ver != MEOW_IPC_VERSION) { logmsg("ipc: bad version %u", ver); return -1; } if (len > MEOW_IPC_MAX) { logmsg("ipc: frame len %u too long", len); return -1; } c->pay_tot = len; c->pay_n = 0; if (len == 0) { ipc_enqueue_frame(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; } ipc_enqueue_frame(c); c->hdr_n = 0; c->pay_n = 0; } } static void *ipc_thread(void *arg) { (void)arg; int lfd = socket(AF_UNIX, SOCK_STREAM, 0); if (lfd < 0) { logmsg("ipc: socket() fail %s", strerror(errno)); return NULL; } unlink(ipc_sock_path); struct sockaddr_un a; memset(&a, 0, sizeof a); a.sun_family = AF_UNIX; strncpy(a.sun_path, ipc_sock_path, sizeof a.sun_path - 1); if (bind(lfd, (struct sockaddr *)&a, sizeof a) < 0) { logmsg("ipc: bind %s fail %s", ipc_sock_path, strerror(errno)); close(lfd); return NULL; } chmod(ipc_sock_path, 0666); if (listen(lfd, MEOW_IPC_BACKLOG) < 0) { logmsg("ipc: listen fail %s", strerror(errno)); close(lfd); return NULL; } fcntl(lfd, F_SETFL, O_NONBLOCK); ipc_thread_ok = 1; logmsg("ipc: listening on %s (backlog %d)", ipc_sock_path, MEOW_IPC_BACKLOG); struct pollfd fds[MEOW_IPC_CONNS + 1]; int nfds = 1; fds[0].fd = lfd; fds[0].events = POLLIN; fds[0].revents = 0; while (!ipc_shutdown) { int r = poll(fds, nfds, 400); if (r < 0) { if (errno == EINTR) continue; break; } if (r == 0) continue; if (fds[0].revents & POLLIN) { for (;;) { int c = accept(lfd, NULL, NULL); if (c < 0) break; fcntl(c, F_SETFL, O_NONBLOCK); if (nfds <= MEOW_IPC_CONNS) { ipc_conns[nfds - 1].fd = c; ipc_conns[nfds - 1].hdr_n = 0; ipc_conns[nfds - 1].pay_n = 0; ipc_conns[nfds - 1].pay_tot = 0; fds[nfds].fd = c; fds[nfds].events = POLLIN; fds[nfds].revents = 0; nfds++; ipc_conn_count = nfds - 1; logmsg("ipc: conn %d accept (nfds=%d)", c, nfds - 1); } else { close(c); logmsg("ipc: conn %d rejected (full)", c); } } } int i = 1; while (i < nfds) { int ev = fds[i].revents; if (ev & (POLLIN | POLLHUP | POLLERR | POLLNVAL)) { struct meow_ipc_conn *cc = &ipc_conns[i - 1]; if (ipc_conn_read(cc) < 0) { logmsg("ipc: conn %d closed", cc->fd); close(cc->fd); for (int j = i; j < nfds - 1; j++) { fds[j] = fds[j + 1]; ipc_conns[j - 1] = ipc_conns[j]; } nfds--; ipc_conn_count = nfds - 1; continue; } } i++; } for (int j = 0; j < nfds; j++) fds[j].revents = 0; } for (int i = 1; i < nfds; i++) close(fds[i].fd); close(lfd); unlink(ipc_sock_path); logmsg("ipc: thread exit"); return NULL; } /* 启动 IPC 线程(主循环任意时刻调一次)。返回 0=就绪,-1=线程创建失败。 * (bind/listen 失败记 ipc_thread_ok=0,随后 meow_ipc_process 仍安全轮询空队列。) */ int meow_ipc_init(void) { /* 注入媒体会话 ops:send=ipc_send 包装、log=logmsg(阶段D附录B)。 */ static meow_media_ops g_media_ops; g_media_ops.send = meow_ipc_send_pub; g_media_ops.log = logmsg; meow_media_init(&g_media_ops); /* M4:注入后台保活/后台服务 ops(logmsg/sd_device_present/通知清理/熔断发帧)。 */ meow_bgplay_launcher_init(); /* AMSD_DESIGN §4.3:注入 amsd-start ops(launch=launch_app/launch_activity,resolve=resolve_chain)。 */ meow_ams_launcher_init(); ipc_event_fd = eventfd(0, EFD_NONBLOCK); if (ipc_event_fd < 0) logmsg("ipc: eventfd fail %s (10ms poll drains anyway)", strerror(errno)); if (pthread_create(&ipc_pthread, NULL, ipc_thread, NULL) != 0) { logmsg("ipc: pthread_create fail"); return -1; } usleep(60000); /* 给 bind+listen 一点时间,log 就绪状态 */ if (ipc_thread_ok) logmsg("ipc: ready (eventfd=%d)", ipc_event_fd); else logmsg("ipc: NOT ready (bind/listen failed, thread exiting)"); return ipc_thread_ok ? 0 : -1; } /* 主循环:非阻塞排空 eventfd + 处理入队帧(单写者改状态并回包)。 * 事件fd 仅为低延迟优化;若为 -1,10ms 轮询仍保证每圈排空。 */ void meow_ipc_process(void) { /* AMSD_DESIGN §4.3:同步「前台 App 会话」占用位 → meow_ams_pending_start_set 据此 * 在收到 MSG_AMS_START 时正确拒绝(-MEOW_ERR_BUSY),杜绝「报 OK 但未拉起」。 */ meow_ams_set_busy(g_app_session_active); if (ipc_event_fd >= 0) { uint64_t v; while (read(ipc_event_fd, &v, sizeof v) == (ssize_t)sizeof v) {} } while (ipc_head != ipc_tail) { struct meow_ipc_msg *m = &ipc_ring[ipc_head % MEOW_IPC_RING]; ipc_dispatch(m); ipc_head++; } __sync_synchronize(); /* M4:后台表周期维护挂点(主循环单写者排空点,run_menu/前台会话/idle/ipc-only * 四处循环都经此处)——drain 后台 App stdout + 回收/卡拔 + 服务退避重启。 */ meow_bg_housekeep(); } /* ---- AMSD_DESIGN §6.2 二期:launcher → amsd 转发工具(best-effort)---- */ /* hex 编码二进制/字符串到 out(小写 2 字符/字节)。返回编码字节数。 */ static int ams_hex_encode(const unsigned char *src, int slen, char *out, int outsz) { static const char *hx = "0123456789abcdef"; int n = 0; for (int i = 0; i < slen && n + 2 < outsz; i++) { out[n++] = hx[src[i] >> 4]; out[n++] = hx[src[i] & 15]; } out[n] = 0; return n; } /* m_act_declare_t.entries 为 count 个 NUL 结尾 "id=chain" 串的拼接;返回其字节长(含 NUL)。 */ static int declare_entries_len(const m_act_declare_t *d) { const char *p = d->entries; for (int k = 0; k < d->count && *p; k++) { while (*p) p++; p++; } return (int)(p - d->entries); } /* 处理一条入队帧:HELLO→ACK、ACT_QUERY→ACT_RESULT/ERR、ACT_DECLARE→ACK、其他→ERR。 */ static void ipc_dispatch(struct meow_ipc_msg *m) { switch (m->type) { case MSG_HELLO: { m_hello_t h; memset(&h, 0, sizeof h); memcpy(&h, m->payload, m->plen < (uint16_t)sizeof h ? m->plen : (uint16_t)sizeof h); h.app_id[MEOW_APP_ID_MAX - 1] = 0; meow_acct_hello(h.app_id, h.version, h.flags); m_ack_t a; a.code = MEOW_OK; a.status = (uint32_t)g_naccts; ipc_send(m->fd, MSG_ACK, m->req_id, &a, sizeof a); logmsg("ipc: HELLO app=%s ver=%u -> ACK naccts=%d", h.app_id, h.version, g_naccts); break; } case MSG_ACT_DECLARE: { m_act_declare_t d; memset(&d, 0, sizeof d); memcpy(&d, m->payload, m->plen < (uint16_t)sizeof d ? m->plen : (uint16_t)sizeof d); d.app_id[MEOW_APP_ID_MAX - 1] = 0; meow_acct_declare(d.app_id, d.entries, d.count); m_ack_t a; a.code = MEOW_OK; a.status = (uint32_t)g_naccts; ipc_send(m->fd, MSG_ACK, m->req_id, &a, sizeof a); logmsg("ipc: ACT_DECLARE app=%s n=%u -> ACK", d.app_id, d.count); /* AMSD_DESIGN §6.2 二期:运行时 declare 转发 amsd(权威表增量;amsd 不在→静默丢)。 * entries 载荷 hex 编码(含 NUL 分隔),amsd 解码后并集覆盖。 */ { int elen = declare_entries_len(&d); if (elen > 0) { char hex[2 * MEOW_IPC_CARGO + 8]; (void)ams_hex_encode((const unsigned char *)d.entries, elen, hex, sizeof hex); char fwd[2 * MEOW_IPC_CARGO + 80]; int fl = snprintf(fwd, sizeof fwd, "DECLARE %s %d %s\n", d.app_id, (int)d.count, hex); if (fl > 0) meow_ams_send(fwd, fl); } } break; } case MSG_ACT_QUERY: { m_act_query_t q; memset(&q, 0, sizeof q); memcpy(&q, m->payload, m->plen < (uint16_t)sizeof q ? m->plen : (uint16_t)sizeof q); q.app_id[MEOW_APP_ID_MAX - 1] = 0; q.activity[MEOW_ACT_MAX - 1] = 0; char chain[MEOW_CHAIN_MAX] = ""; int depth = 0; int code = resolve_chain(q.app_id, q.activity, chain, &depth); if (code == MEOW_OK) { m_act_result_t r; memset(&r, 0, sizeof r); r.depth = (uint32_t)depth; r.code = MEOW_OK; size_t cl = strlen(chain); if (cl >= MEOW_CHAIN_MAX) cl = MEOW_CHAIN_MAX - 1; memcpy(r.chain, chain, cl); r.chain_len = (uint16_t)cl; ipc_send(m->fd, MSG_ACT_RESULT, m->req_id, &r, sizeof r); logmsg("ipc: ACT_QUERY app=%s act=%s -> depth=%d chain=%s", q.app_id, q.activity, depth, chain); } else { m_err_t e; memset(&e, 0, sizeof e); e.code = (uint32_t)code; const char *msg = (code == MEOW_ERR_NOENT) ? "unregistered app/activity" : "resolve failed"; e.msg_len = (uint16_t)strlen(msg); memcpy(e.msg, msg, e.msg_len); ipc_send(m->fd, MSG_ERR, m->req_id, &e, sizeof e); logmsg("ipc: ACT_QUERY app=%s act=%s -> ERR(0x%x)", q.app_id, q.activity, code); } break; } case MSG_ACT_FOCUS: { m_act_focus_t f; memset(&f, 0, sizeof f); memcpy(&f, m->payload, m->plen < (uint16_t)sizeof f ? m->plen : (uint16_t)sizeof f); f.app_id[MEOW_APP_ID_MAX - 1] = 0; f.activity[MEOW_ACT_MAX - 1] = 0; meow_fg_act_set(f.app_id, f.activity); /* M2.17:当前顶层 activity(keep_awake 判定) */ m_ack_t a; a.code = MEOW_OK; a.status = (uint32_t)g_naccts; ipc_send(m->fd, MSG_ACK, m->req_id, &a, sizeof a); logmsg("ipc: ACT_FOCUS app=%s act=%s -> ACK", f.app_id, f.activity); break; } case MSG_ACT_BACK: { meow_fg_act_back(); /* 回到根/无 activity:清 keep_awake */ m_ack_t a; a.code = MEOW_OK; a.status = (uint32_t)g_naccts; ipc_send(m->fd, MSG_ACK, m->req_id, &a, sizeof a); logmsg("ipc: ACT_BACK -> ACK"); break; } /* M3 导航栈(FRAMEWORK_DESIGN §3):前台应用上报导航(REPORT)或请求退出(EXIT)。 * REPORT → 更新 nav_stack 深度/顶层;EXIT → 置退出请求,前台 waitpid 循环终止应用回主屏。 * 均在主循环单写者上下文执行(meow_ipc_process 排空处;launch_* 前台循环也调 meow_ipc_process)。 */ case MSG_NAV: { m_nav_t n; memset(&n, 0, sizeof n); memcpy(&n, m->payload, m->plen < (uint16_t)sizeof n ? m->plen : (uint16_t)sizeof n); n.app_id[MEOW_APP_ID_MAX - 1] = 0; n.current[MEOW_ACT_MAX - 1] = 0; if (n.nav_type == MEOW_NAV_REPORT) meow_nav_report(n.app_id, n.depth, n.current); else meow_nav_request_exit(); /* MEOW_NAV_EXIT(含未知值防御性按退出处理) */ m_ack_t a; a.code = MEOW_OK; a.status = (uint32_t)n.depth; ipc_send(m->fd, MSG_ACK, m->req_id, &a, sizeof a); logmsg("ipc: NAV app=%s type=%u depth=%u top=\"%s\" -> ACK", n.app_id, n.nav_type, n.depth, n.current); break; } /* M4 后台服务注册(FRAMEWORK_DESIGN §5/§9):无头服务 spawn/退避重启/熔断。 * 校验链:acct 存在且 exec 非空(NOENT)→ 权限(app.json background=true 或 * flags 含 MF_BACKGROUND,否则 PERM)→ service. 静态声明存在(NOENT)→ * meow_svc_register 拉起。成功回 MSG_SERVICE_OK(m_ack_t:code=0,status=pid); * 失败回 MSG_ERR(仿 ACT_QUERY 模式)。注册后调用方须保持连接(熔断广播投递判定)。 */ case MSG_REGISTER: { m_register_t r; memset(&r, 0, sizeof r); memcpy(&r, m->payload, m->plen < (uint16_t)sizeof r ? m->plen : (uint16_t)sizeof r); r.app_id[MEOW_APP_ID_MAX - 1] = 0; r.svc[15] = 0; struct meow_acct *ac = meow_acct_find(r.app_id); const struct meow_svc *sv = ac ? meow_acct_svc_find(ac, r.svc) : NULL; int code = MEOW_OK; int pid = 0; if (!ac || !ac->exec[0]) code = MEOW_ERR_NOENT; else if (!(meow_acct_background_perm(ac) & 1) && !(r.flags & MF_BACKGROUND)) code = MEOW_ERR_PERM; else if (!sv) code = MEOW_ERR_NOENT; else { char sexec[160]; meow_svc_exec_path(ac->exec, r.svc, sexec, sizeof sexec); pid = meow_svc_register(r.app_id, r.svc, sexec, sv->restart, sv->max, sv->window_ms, strncmp(sexec, MEOWAPPS, strlen(MEOWAPPS)) == 0, m->fd); if (pid < 0) { code = pid; pid = 0; } } if (code == MEOW_OK) { m_ack_t a; a.code = MEOW_OK; a.status = (uint32_t)pid; /* 服务子进程 pid */ ipc_send(m->fd, MSG_SERVICE_OK, m->req_id, &a, sizeof a); logmsg("ipc: REGISTER app=%s svc=%s -> SERVICE_OK pid=%d", r.app_id, r.svc, pid); } else { m_err_t e; memset(&e, 0, sizeof e); e.code = (uint32_t)code; const char *msg = (code == MEOW_ERR_PERM) ? "no background permission" : (code == MEOW_ERR_EXISTS) ? "service already running" : (code == MEOW_ERR_NOMEM) ? "service slots full" : "app/service not found"; e.msg_len = (uint16_t)strlen(msg); memcpy(e.msg, msg, e.msg_len); ipc_send(m->fd, MSG_ERR, m->req_id, &e, sizeof e); logmsg("ipc: REGISTER app=%s svc=%s -> ERR(%d)", r.app_id, r.svc, code); } break; } /* M4:请求停止自己已注册的无头服务(SIGTERM,标记 stopping 不触发退避重启)。 */ case MSG_SERVICE_STOP: { m_service_stop_t s; memset(&s, 0, sizeof s); memcpy(&s, m->payload, m->plen < (uint16_t)sizeof s ? m->plen : (uint16_t)sizeof s); s.app_id[MEOW_APP_ID_MAX - 1] = 0; s.svc[15] = 0; int rc = meow_svc_stop(s.app_id, s.svc); m_ack_t a; a.code = (uint32_t)(rc < 0 ? -rc : MEOW_OK); a.status = 0; ipc_send(m->fd, MSG_ACK, m->req_id, &a, sizeof a); logmsg("ipc: SERVICE_STOP app=%s svc=%s -> ACK code=%u", s.app_id, s.svc, a.code); break; } /* AMSD_DESIGN §4.3:amsd `am start`/`am kill` 控制请求。主循环单写者仅置位 pending * (run_menu 前台循环 / launch_* waitpid 循环消费),不回执启动结果(一期 best-effort)。 */ case MSG_AMS_START: { m_ams_start_t s; memset(&s, 0, sizeof s); memcpy(&s, m->payload, m->plen < (uint16_t)sizeof s ? m->plen : (uint16_t)sizeof s); s.app_id[MEOW_APP_ID_MAX - 1] = 0; s.activity[MEOW_ACT_MAX - 1] = 0; s.picker_action[23] = 0; s.picker_caller[MEOW_APP_ID_MAX - 1] = 0; /* §6.2 二期:Picker 上下文(picker=1 且 action 非空)→ 置位武装位,launch_app_chain 注入 env */ int rc; if (s.picker) rc = meow_ams_pending_start_set_ex(s.app_id, s.activity, s.picker_action, s.picker_req_id, s.picker_caller); else rc = meow_ams_pending_start_set(s.app_id, s.activity); m_ack_t a; a.code = (uint32_t)(rc < 0 ? -rc : MEOW_OK); /* 0=OK;MEOW_ERR_BUSY=有前台 App */ a.status = 0; ipc_send(m->fd, MSG_ACK, m->req_id, &a, sizeof a); logmsg("ipc: AMS_START app=%s act=\"%s\" picker=%u req=%u -> ACK code=%u", s.app_id, s.activity, s.picker, s.picker_req_id, a.code); break; } case MSG_AMS_KILL: { m_ams_kill_t k; memset(&k, 0, sizeof k); memcpy(&k, m->payload, m->plen < (uint16_t)sizeof k ? m->plen : (uint16_t)sizeof k); k.app_id[MEOW_APP_ID_MAX - 1] = 0; m_ack_t a; if (g_app_session_active) { meow_ams_pending_kill_set(k.app_id); a.code = MEOW_OK; } else { a.code = (uint32_t)MEOW_ERR_NOENT; /* 无前台 App 可终止 */ } a.status = 0; ipc_send(m->fd, MSG_ACK, m->req_id, &a, sizeof a); logmsg("ipc: AMS_KILL app=%s -> ACK code=%u", k.app_id, a.code); break; } /* AMSD_DESIGN §6.2 二期:picker 跨 App 调用(App A → launcher → amsd 路由)。 * launcher 仅转发 amsd + ACK 发起方(best-effort;结果经 MSG_PICKER_RESULT 回路记录)。 * 诚实出入:A 发送后即断开,amsd 结果暂时落记录(picker-status 可查),push 回 A 需 * 将来 MSG_REGISTER 会话通道(M4)。 */ case MSG_PICKER_CALL: { m_picker_call_t p; memset(&p, 0, sizeof p); memcpy(&p, m->payload, m->plen < (uint16_t)sizeof p ? m->plen : (uint16_t)sizeof p); p.caller[MEOW_APP_ID_MAX - 1] = 0; p.action[23] = 0; m_ack_t a; a.code = MEOW_OK; a.status = 0; ipc_send(m->fd, MSG_ACK, m->req_id, &a, sizeof a); char fwd[96]; int fl = snprintf(fwd, sizeof fwd, "PICKER_CALL %s %u %s\n", p.caller, p.req_id, p.action); if (fl > 0) meow_ams_send(fwd, fl); logmsg("ipc: PICKER_CALL caller=%s req=%u act=%s -> ACK", p.caller, p.req_id, p.action); break; } /* AMSD_DESIGN §6.2 二期:picker 结果回传(handler App → launcher → amsd 记录)。 * value 可能含空格 → hex 编码,amsd 解码。 */ case MSG_PICKER_RESULT: { m_picker_result_t r; memset(&r, 0, sizeof r); memcpy(&r, m->payload, m->plen < (uint16_t)sizeof r ? m->plen : (uint16_t)sizeof r); r.caller[MEOW_APP_ID_MAX - 1] = 0; r.action[23] = 0; m_ack_t a; a.code = MEOW_OK; a.status = 0; ipc_send(m->fd, MSG_ACK, m->req_id, &a, sizeof a); char hex[512 + 8]; (void)ams_hex_encode((const unsigned char *)r.value, (int)strlen(r.value), hex, sizeof hex); char fwd[96 + 512 + 8]; int fl = snprintf(fwd, sizeof fwd, "PICKER_RESULT %u %s %u %s\n", r.req_id, r.action, r.code, hex); if (fl > 0) meow_ams_send(fwd, fl); logmsg("ipc: PICKER_RESULT req=%u act=%s code=%u value=\"%s\"", r.req_id, r.action, r.code, r.value); break; } /* M2a:通知存储(NOTIFICATION_SYSTEM.md §4/§7/§8)。IPC 线程只入队,此处在主循环 * 单写者落存储 + 回 ACK(与 HELLO/QUERY 同一线程模型,绝不在 socket IO 阻塞)。 */ case MSG_NOTIFY: { m_notify_t mn; memset(&mn, 0, sizeof mn); memcpy(&mn, m->payload, m->plen < (uint16_t)sizeof mn ? m->plen : (uint16_t)sizeof mn); mn.app_id[MEOW_APP_ID_MAX - 1] = 0; mn.category[23] = 0; mn.summary[63] = 0; mn.body[95] = 0; mn.app_icon[47] = 0; mn.target_activity[MEOW_ACT_MAX - 1] = 0; /* 映射到存储结构(freedesktop + MeowOS 扩展;actions/hints v1 空) */ meow_notif_t n; memset(&n, 0, sizeof n); n.id = mn.replaces_id; /* replaces_id==0 => 新建;>0 => 替换(不存在则新建) */ snprintf(n.app_id, sizeof n.app_id, "%s", mn.app_id); snprintf(n.category, sizeof n.category, "%s", mn.category); snprintf(n.summary, sizeof n.summary, "%s", mn.summary); snprintf(n.body, sizeof n.body, "%s", mn.body); snprintf(n.app_icon, sizeof n.app_icon, "%s", mn.app_icon); snprintf(n.target_activity, sizeof n.target_activity, "%s", mn.target_activity); n.expire_timeout = mn.expire_timeout; n.progress = mn.progress; n.urgency = mn.urgency; /* 0-3(低/普通/高/紧急);旧调用=0 → LOW(静默),新调用缺省 NORMAL */ n.ongoing = mn.ongoing; n.media = mn.media; uint32_t nid = 0; int rc = meow_notif_post(&n, &nid); m_ack_t a; a.code = (rc == NOTIF_OK) ? MEOW_OK : (uint32_t)rc; a.status = nid; /* 返回分配/更新的通知 id */ ipc_send(m->fd, MSG_NOTIFY_ACK, m->req_id, &a, sizeof a); logmsg("ipc: NOTIFY app=%s cat=%s id=%u rc=%d -> ACK", mn.app_id, mn.category, nid, rc); break; } case MSG_NOTIFY_DISMISS: { uint32_t id = 0; if (m->plen >= 4) memcpy(&id, m->payload, 4); int rc = meow_notif_dismiss(id); m_ack_t a; a.code = (rc == NOTIF_OK) ? MEOW_OK : (uint32_t)rc; a.status = 0; ipc_send(m->fd, MSG_ACK, m->req_id, &a, sizeof a); logmsg("ipc: NOTIFY_DISMISS id=%u rc=%d -> ACK", id, rc); break; } case MSG_NOTIFY_PROGRESS: { m_notify_progress_t p; memset(&p, 0, sizeof p); memcpy(&p, m->payload, m->plen < (uint16_t)sizeof p ? m->plen : (uint16_t)sizeof p); /* 实时进度:仅改 launcher 内存活跃槽(不落盘);progress>=100 视为结束 → 里程碑落盘 */ int rc = meow_notif_update_progress(p.nid, p.progress); m_ack_t a; a.code = (rc == NOTIF_OK) ? MEOW_OK : (uint32_t)rc; a.status = 0; ipc_send(m->fd, MSG_ACK, m->req_id, &a, sizeof a); 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; } /* §10.5:App 前台顶部下滑 → 请求展开通知中心叠加会话。仅在 App 前台会话(launch_app * waitpid 循环)期间受理(置 pending 让该循环消费后展开叠加);否则回 ACK(status=BUSY) * 让客户端知情但不展开(主循环/无 App 前台不展开)。App 不强依赖本 ACK(best-effort)。 */ case MSG_NOTIF_OPEN: { m_notif_open_t o; memset(&o, 0, sizeof o); memcpy(&o, m->payload, m->plen < (uint16_t)sizeof o ? m->plen : (uint16_t)sizeof o); o.app_id[MEOW_APP_ID_MAX - 1] = 0; m_ack_t a; a.code = MEOW_OK; a.status = 0; if (g_app_session_active && !g_notif_open_pending) { g_notif_open = o; /* 单写者置位;waitpid 循环 take 后展开 */ g_notif_open_pending = 1; a.status = 1; /* 已受理入队 */ logmsg("ipc: NOTIF_OPEN app=%s oy=%u hz=%u -> queued", o.app_id, o.origin_y, o.hotzone); } else { a.status = 0xFFFFFFFFu; /* 无 App 前台 / 已有会话在跑:不展开 */ logmsg("ipc: NOTIF_OPEN app=%s oy=%u hz=%u -> ignored (no app session or busy)", o.app_id, o.origin_y, o.hotzone); } ipc_send(m->fd, MSG_ACK, m->req_id, &a, sizeof a); break; } /* §10.5:App 告知结束/退出 → launcher 收尾兜底(清 pending/标记;非可靠链路,launcher 主要 * 靠 waitpid/kill(pid,0) 自感知)。best-effort,回 ACK。 */ case MSG_NOTIF_CLOSE: { m_notif_close_t o; memset(&o, 0, sizeof o); memcpy(&o, m->payload, m->plen < (uint16_t)sizeof o ? m->plen : (uint16_t)sizeof o); o.app_id[MEOW_APP_ID_MAX - 1] = 0; meow_notif_open_clear(); m_ack_t a; a.code = MEOW_OK; a.status = (uint32_t)o.reason; ipc_send(m->fd, MSG_ACK, m->req_id, &a, sizeof a); logmsg("ipc: NOTIF_CLOSE app=%s reason=%u -> ACK", o.app_id, o.reason); break; } case MSG_NOTIFY_LIST: /* ↓ 守护→App 下发通知清单(重同步)。本帧不来自客户端(无请求类型),故不在此应答; * 需用时由守护主动经 meow_notif_serialize_list() + ipc_send 下发(M5 通知中心接入)。 */ logmsg("ipc: NOTIFY_LIST (downlink) not dispatched here"); break; /* 阶段D附录B:媒体会话(FRAMEWORK_DESIGN 附录 B)。META 声明 MF_PLAYBACK 的 App 上报 * 元数据/进度/状态 → 归并到 meow_media 会话(最近活跃=当前源);回 ACK。 */ case MSG_META: { m_meta_t mm; memset(&mm, 0, sizeof mm); memcpy(&mm, m->payload, m->plen < (uint16_t)sizeof mm ? m->plen : (uint16_t)sizeof mm); mm.app_id[MEOW_APP_ID_MAX - 1] = 0; mm.title[127] = 0; mm.artist[127] = 0; mm.album[127] = 0; mm.artwork[191] = 0; mm.lyric[191] = 0; int rc = meow_media_meta(&mm, m->fd); m_ack_t a; a.code = MEOW_OK; /* 会话策略:槽满降级最旧,不视为错误 */ a.status = (uint32_t)rc; ipc_send(m->fd, MSG_ACK, m->req_id, &a, sizeof a); logmsg("ipc: META app=%s state=%u pos=%u/%u rc=%d -> ACK", mm.app_id, mm.state, mm.pos_ms, mm.dur_ms, rc); break; } /* MSG_PLAYCTL(↑ 控制器→launcher 转发):把 cmd/seek 下发到「最近活跃媒体会话」fd。 * 物理媒体键 / 宿主验证经此外发;通知中心卡片/播放页按钮走 meow_media_playctl 直发。 */ case MSG_PLAYCTL: { m_playctl_t c; memset(&c, 0, sizeof c); memcpy(&c, m->payload, m->plen < (uint16_t)sizeof c ? m->plen : (uint16_t)sizeof c); int rc = meow_media_playctl(c.cmd, c.seek_ms); m_ack_t a; a.code = MEOW_OK; a.status = (uint32_t)rc; /* 0=已下发;-1=无源/发送失败(status=0xFFFFFFFF) */ ipc_send(m->fd, MSG_ACK, m->req_id, &a, sizeof a); logmsg("ipc: PLAYCTL cmd=%u seek=%lld rc=%d -> ACK", c.cmd, (long long)c.seek_ms, rc); break; } /* 阶段D·批3:媒体会话歌词上报(App 播放时把 lrc/tlrc 上报到会话)。分片经 * meow_media_lyric 归并(有界缓冲+seq 代际);回 ACK。 */ case MSG_META_LYRIC: { m_lyric_t l; memset(&l, 0, sizeof l); memcpy(&l, m->payload, m->plen < (uint16_t)sizeof l ? m->plen : (uint16_t)sizeof l); l.app_id[MEOW_APP_ID_MAX - 1] = 0; int rc = meow_media_lyric(&l); m_ack_t a; a.code = MEOW_OK; /* 会话策略:无会话丢弃(rc=-1),不视为错误 */ a.status = (uint32_t)rc; ipc_send(m->fd, MSG_ACK, m->req_id, &a, sizeof a); logmsg("ipc: META_LYRIC app=%s kind=%u seq=%u total=%u off=%u len=%u rc=%d -> ACK", l.app_id, l.kind, l.seq, l.total, l.off, l.len, rc); break; } default: logmsg("ipc: unsupported frame type 0x%04x (req %u)", m->type, m->req_id); { m_err_t e; memset(&e, 0, sizeof e); e.code = MEOW_ERR_INVAL; char msg[40]; snprintf(msg, sizeof msg, "unsupported type 0x%04x", m->type); e.msg_len = (uint16_t)strlen(msg); memcpy(e.msg, msg, e.msg_len); ipc_send(m->fd, MSG_ERR, m->req_id, &e, sizeof e); } break; } }