refactor(panel): 前后端按模块拆分+屏幕/控制合并页 — 后端meowpanel.c 6372行拆src/*.c+mp.h(235函数原型,20端点output byte-identical,0warning)+前端拆mp_shared/mp_pages/meowpanel三层;屏幕·遥控合并(左屏实时画面+右MD3 SVG矢量按钮无emoji);重编后热更TF卡

This commit is contained in:
AskaEth committed 2026-09-04 00:56:13 +08:00
1 parent f3ae06d7cd
commit 2dc4cad454
26 files changed
+9317 -2755

No files matched your search

+277
View File
@@ -0,0 +1,277 @@
/* mp_screen.c — 实时画面域:/api/screen.jpeg(fb0→JPEG,增量帧)+ 触摸注入 /api/input/tap。 */
#include "mp.h"
static uint8_t *g_screen_prev = NULL; /* 上一帧紧凑 RGB565 */
static int g_screen_pw = 0, g_screen_ph = 0;
static int64_t g_last_self_inject_ms = 0;
#define TOUCH_ACTIVE_MS 5000
int screen_read_frame(const char *dev, uint8_t **out, int *w, int *h)
{
int fd = open(dev, O_RDONLY);
if (fd < 0) return -1;
unsigned char vinfo[160], finfo[68];
memset(vinfo, 0, sizeof vinfo);
memset(finfo, 0, sizeof finfo);
if (ioctl(fd, FBIOGET_VSCREENINFO, vinfo) < 0 ||
ioctl(fd, FBIOGET_FSCREENINFO, finfo) < 0) { close(fd); return -1; }
uint32_t xres = *(uint32_t *)(vinfo + 0);
uint32_t yres = *(uint32_t *)(vinfo + 4);
uint32_t yoff = *(uint32_t *)(vinfo + 20);
uint32_t bpp = *(uint32_t *)(vinfo + 24);
uint32_t line = *(uint32_t *)(finfo + 44);
if (!line) line = xres * (bpp / 8);
if (bpp != 16 || xres == 0 || yres == 0 || line < xres * 2) { close(fd); return -1; }
size_t framelen = (size_t)line * (size_t)yres;
uint8_t *frame = (uint8_t *)malloc(framelen);
if (!frame) { close(fd); return -1; }
ssize_t got = pread(fd, frame, framelen, (off_t)line * (off_t)yoff);
close(fd);
if (got != (ssize_t)framelen) { free(frame); return -1; }
/* 若 line != xres*2(行尾填充),压缩为紧凑 RGB565(xres*yres*2) */
if (line != xres * 2) {
size_t cplen = (size_t)xres * 2;
for (uint32_t y = 0; y < yres; y++)
memmove(frame + (size_t)y * cplen, frame + (size_t)y * line, cplen);
}
*out = frame; *w = (int)xres; *h = (int)yres;
return 0;
}
int fb_dims(const char *dev, int *w, int *h)
{
int fd = open(dev, O_RDONLY);
if (fd < 0) return -1;
unsigned char vinfo[160];
memset(vinfo, 0, sizeof vinfo);
if (ioctl(fd, FBIOGET_VSCREENINFO, vinfo) < 0) { close(fd); return -1; }
uint32_t xres = *(uint32_t *)(vinfo + 0);
uint32_t yres = *(uint32_t *)(vinfo + 4);
close(fd);
if (!xres || !yres) return -1;
*w = (int)xres; *h = (int)yres;
return 0;
}
void screen_prev_store(const uint8_t *frame, int w, int h)
{
size_t need = (size_t)w * (size_t)h * 2;
if (w != g_screen_pw || h != g_screen_ph) {
free(g_screen_prev);
g_screen_prev = NULL;
g_screen_pw = w; g_screen_ph = h;
g_screen_prev = (uint8_t *)malloc(need);
}
if (g_screen_prev) memcpy(g_screen_prev, frame, need);
}
int screen_diff_blocks(const uint8_t *cur, const uint8_t *prev, int w, int h,
int *bx, int *by, int *bw, int *bh, int *total_blocks)
{
const int B = SCREEN_BLOCK;
int nbx = (w + B - 1) / B, nby = (h + B - 1) / B;
if (total_blocks) *total_blocks = nbx * nby;
int changed = 0;
int minx = nbx, miny = nby, maxx = -1, maxy = -1;
for (int byi = 0; byi < nby; byi++) {
for (int bxi = 0; bxi < nbx; bxi++) {
int x0 = bxi * B, y0 = byi * B;
int wdt = B, hgt = B;
if (x0 + wdt > w) wdt = w - x0;
if (y0 + hgt > h) hgt = h - y0;
int diff = 0;
for (int y = 0; y < hgt && !diff; y++) {
const uint8_t *a = cur + ((size_t)(y0 + y) * (size_t)w + (size_t)x0) * 2;
const uint8_t *b = prev + ((size_t)(y0 + y) * (size_t)w + (size_t)x0) * 2;
if (memcmp(a, b, (size_t)wdt * 2) != 0) diff = 1;
}
if (diff) {
changed++;
if (bxi < minx) minx = bxi;
if (byi < miny) miny = byi;
if (bxi > maxx) maxx = bxi;
if (byi > maxy) maxy = byi;
}
}
}
if (changed) { *bx = minx; *by = miny; *bw = maxx - minx + 1; *bh = maxy - miny + 1; }
return changed;
}
int api_screen_jpeg(int sock, const req_t *r)
{
int q = 70; /* 默认质量(体积/清晰折中) */
char qv[8];
if (qparam(r->query, "q", qv, sizeof qv)) {
int t = atoi(qv);
if (t >= 1 && t <= 100) q = t;
}
char dv[8];
int delta = (qparam(r->query, "delta", dv, sizeof dv) && atoi(dv) == 1);
const char *dev = getenv("MEOW_FB_DEV"); if (!dev || !dev[0]) dev = FB_DEV;
uint8_t *frame = NULL; int w = 0, h = 0;
if (screen_read_frame(dev, &frame, &w, &h) != 0)
return send_err(sock, 500, "INTERNAL", "读取帧缓冲失败(/dev/fb0)");
if (!delta) { /* 默认整帧(不破坏原行为) */
uint8_t *jpeg = NULL; size_t jlen = 0;
int rc = mp_jpeg_encode_rgb565(frame, w, h, q, &jpeg, &jlen);
if (rc != 0) { free(frame); return send_err(sock, 500, "INTERNAL", "JPEG 编码失败"); }
screen_prev_store(frame, w, h); /* 维护 delta 基线 */
int ret = send_raw(sock, 200, "image/jpeg", jpeg, jlen);
free(jpeg);
free(frame);
return ret;
}
/* ---- delta 模式 ---- */
const int B = SCREEN_BLOCK;
int changed = 0, bx = 0, by = 0, bw = 0, bh = 0, total = 0;
int force_full = (!g_screen_prev || g_screen_pw != w || g_screen_ph != h);
if (!force_full) {
changed = screen_diff_blocks(frame, g_screen_prev, w, h, &bx, &by, &bw, &bh, &total);
if (bw * B >= w && bh * B >= h) force_full = 1; /* 全屏变化 → 直接发整帧 */
} else {
total = ((w + B - 1) / B) * ((h + B - 1) / B);
}
screen_prev_store(frame, w, h); /* 基线更新为当前帧 */
char hdr[224];
int ret = -1;
if (changed == 0 && !force_full) { /* 无变化:空 body,前端跳过叠加 */
snprintf(hdr, sizeof hdr, "X-Meow-Delta: 1\r\nX-Meow-Full: 0\r\nX-Meow-W: %d\r\nX-Meow-H: %d\r\n"
"X-Meow-Changed: 0\r\nX-Meow-Blocks: %d", w, h, total);
ret = send_head(sock, 200, "image/jpeg", 0, hdr);
free(frame);
return ret;
}
if (force_full) { /* 冷藏起/几何变化/全屏变化 → 整帧 */
uint8_t *jpeg = NULL; size_t jlen = 0;
int rc = mp_jpeg_encode_rgb565(frame, w, h, q, &jpeg, &jlen);
if (rc != 0) { free(frame); return send_err(sock, 500, "INTERNAL", "JPEG 编码失败"); }
snprintf(hdr, sizeof hdr, "X-Meow-Delta: 1\r\nX-Meow-Full: 1\r\nX-Meow-W: %d\r\nX-Meow-H: %d\r\n"
"X-Meow-Changed: %d\r\nX-Meow-Blocks: %d", w, h, changed ? changed : total, total);
ret = send_head(sock, 200, "image/jpeg", (long long)jlen, hdr);
if (ret == 0 && jlen && write_full(sock, jpeg, jlen) < 0) ret = -1;
free(jpeg);
free(frame);
return ret;
}
/* 子图 = 变化块 bbox 外接矩形(8 对齐,钳到屏内),重编码为独立 JPEG 并以响应头回传偏移。 */
int x0 = bx * B, y0 = by * B;
int sw = bw * B, sh = bh * B;
if (x0 + sw > w) sw = w - x0;
if (y0 + sh > h) sh = h - y0;
uint8_t *sub = (uint8_t *)malloc((size_t)sw * (size_t)sh * 2);
if (!sub) { free(frame); return send_err(sock, 500, "INTERNAL", "内存不足"); }
for (int y = 0; y < sh; y++)
memcpy(sub + (size_t)y * (size_t)sw * 2,
frame + ((size_t)(y0 + y) * (size_t)w + (size_t)x0) * 2,
(size_t)sw * 2);
uint8_t *jpeg = NULL; size_t jlen = 0;
int rc = mp_jpeg_encode_rgb565(sub, sw, sh, q, &jpeg, &jlen);
free(sub);
if (rc != 0) { free(frame); return send_err(sock, 500, "INTERNAL", "JPEG 编码失败"); }
snprintf(hdr, sizeof hdr, "X-Meow-Delta: 1\r\nX-Meow-Full: 0\r\nX-Meow-W: %d\r\nX-Meow-H: %d\r\n"
"X-Meow-Sub-X: %d\r\nX-Meow-Sub-Y: %d\r\nX-Meow-Sub-W: %d\r\nX-Meow-Sub-H: %d\r\n"
"X-Meow-Changed: %d\r\nX-Meow-Blocks: %d", w, h, x0, y0, sw, sh, changed, total);
ret = send_head(sock, 200, "image/jpeg", (long long)jlen, hdr);
if (ret == 0 && jlen && write_full(sock, jpeg, jlen) < 0) ret = -1;
free(jpeg);
free(frame);
return ret;
}
int64_t touch_last_local_ms(const char *dev)
{
int fd = open(dev, O_RDONLY | O_NONBLOCK);
if (fd < 0) return 0;
int64_t last = 0;
unsigned char ev[16];
int64_t inject_deadline = g_last_self_inject_ms + 200; /* 注入事件时间 ≈ 注入时刻 */
while (read(fd, ev, sizeof ev) == (ssize_t)sizeof ev) {
int64_t sec = (int64_t)(*(int32_t *)(ev + 0));
int64_t usec = (int64_t)(*(int32_t *)(ev + 4));
int64_t t = sec * 1000 + usec / 1000;
if (t <= inject_deadline) continue; /* 自注入,跳过 */
if (t > last) last = t;
}
close(fd);
return last;
}
int inject_tap(const char *dev, int x, int y)
{
int fd = open(dev, O_WRONLY);
if (fd < 0) return -1;
struct input_event ev;
#define __WE(t_, c_, v_) do { memset(&ev, 0, sizeof ev); ev.type = (t_); ev.code = (c_); \
ev.value = (v_); if (write(fd, &ev, sizeof ev) != (ssize_t)sizeof ev) { close(fd); return -1; } } while (0)
/* 事件顺序:先 BTN_TOUCH=1(按下边沿),后 ABS 坐标 —— launcher/meow_touch 在按下边沿
* 会重置坐标 (-1),只在 down 之后到来的首个有效 ABS 才锁原点(goodix 时序免疫)。若先发
* ABS 再发 BTN_TOUCH,原点永不锁定 → 抬手表被判为无效 tap(只高亮不启动)。 */
__WE(EV_KEY, BTN_TOUCH, 1);
__WE(EV_ABS, ABS_MT_SLOT, 0);
__WE(EV_ABS, ABS_MT_TRACKING_ID, 7);
__WE(EV_ABS, ABS_MT_POSITION_X, x);
__WE(EV_ABS, ABS_MT_POSITION_Y, y);
__WE(EV_ABS, ABS_MT_TOUCH_MAJOR, 6);
__WE(EV_ABS, ABS_X, x);
__WE(EV_ABS, ABS_Y, y);
__WE(EV_SYN, SYN_REPORT, 0);
usleep(100000); /* 按住 100ms 再抬起,抬起到按下的边沿被 launcher 视为一次 tap */
__WE(EV_KEY, BTN_TOUCH, 0);
__WE(EV_ABS, ABS_MT_TRACKING_ID, -1);
__WE(EV_SYN, SYN_REPORT, 0);
#undef __WE
close(fd);
return 0;
}
int api_input_tap(int sock, const req_t *r)
{
if (!r->body || !r->body[0]) return send_err(sock, 400, "BAD_REQUEST", "缺少 body");
jv_t *root = jv_parse(r->body, (int)r->body_len);
if (!root) return send_err(sock, 400, "BAD_REQUEST", "body 非合法 JSON");
jv_t *jx = jv_obj_get(root, "x");
jv_t *jy = jv_obj_get(root, "y");
if (!jx || jx->type != J_NUM || !jy || jy->type != J_NUM) {
jv_free(root);
return send_err(sock, 400, "BAD_REQUEST", "缺 x/y(数值)");
}
int x = (int)jx->inum, y = (int)jy->inum;
jv_free(root);
const char *dev = getenv("MEOW_TOUCH_DEV"); if (!dev || !dev[0]) dev = TOUCH_DEV;
/* 坐标 clamp 到屏幕范围(前端已按 480x720 映射,此处再兜底) */
int sw = 480, sh = 720;
{ int fx = 0, fy = 0; const char *fbdev = getenv("MEOW_FB_DEV"); if (!fbdev || !fbdev[0]) fbdev = FB_DEV;
if (fb_dims(fbdev, &fx, &fy) == 0) { sw = fx; sh = fy; } }
if (x < 0) x = 0;
if (x >= sw) x = sw - 1;
if (y < 0) y = 0;
if (y >= sh) y = sh - 1;
/* 仲裁:本机最近触摸后 5s 内忽略注入 */
int64_t now = now_mono_ms();
int64_t last = touch_last_local_ms(dev);
if (last > 0 && (now - last) < TOUCH_ACTIVE_MS) {
sbuf_t s = {0};
sA(&s, "{\"dispatched\":false,\"reason\":\"local_touch_active\",\"cooldown_ms\":%lld}",
(long long)(TOUCH_ACTIVE_MS - (now - last)));
int ret = send_json(sock, 200, s.b ? s.b : "");
sbuf_free(&s);
return ret;
}
g_last_self_inject_ms = now_mono_ms();
if (inject_tap(dev, x, y) < 0)
return send_err(sock, 500, "INTERNAL", "触摸注入失败(无法打开触摸设备)");
sbuf_t s = {0};
sA(&s, "{\"dispatched\":true,\"x\":%d,\"y\":%d}", x, y);
int ret = send_json(sock, 200, s.b ? s.b : "");
sbuf_free(&s);
return ret;
}