Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/util.c
T

1406 lines
56 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* util.c — MeowOS launcher 拆分模块(LAUNCHER_SPLIT_PLAN 拆分)。*/
/* 本文件由拆分工具从 meow_launcherd.c 按职责搬移;逻辑与原文 0-diff(仅 static 可见性按跨模块需求调整)。*/
#include "meow_launcher_internal.h"
/* M2.x 应用日志目录简化:一次性迁移 logs/apps/<id>/logs → logs/<id>(定义见下,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 */
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/<id>/logs → logs/<id>(幂等,见下) */
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/<id>/ 用(沿用 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/<id>/logs/<file>.log
* 并入新布局 LOG_DIR_CARD/<id>/<file>.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/<id>/logs/<file>.log → logs/<id>/<file>.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/<id>/ 下,一并并入(跳过已处理的 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/<id> 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 == '.';
}
/* 极简 JSON/扁平值提取:在一行里找 key,读取其值(跳过 ':'/'=' 与可选引号)。
* 支持 JSON `"name": "Hello"` 与扁平 `framework=true`。返回拷贝到 out 的字符数。
* 返回 0=未命中/空。 */
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;
return i;
}
/* 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();
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;
}
/* 全量重抓(进 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;
char st[16];
read_sys_str("/sys/class/power_supply/battery/status", st, sizeof st);
g_sys.charging = (strstr(st, "Charg") || strstr(st, "Full")) ? 1 : 0;
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 <device_ip>/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;
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 一次,不阻塞主循环) */
}
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 };
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];
/* §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++;
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--;
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);
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)
{
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();
}
/* 处理一条入队帧: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);
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;
}
/* 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;
int 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\" -> ACK code=%u", s.app_id, s.activity, 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;
}
/* 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;
}
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;
}
}