Files
MeowOS-For-R3P2/patch_v2/meowpanel/src/mp_screen.c
T
AskaEth f40abbd494 feat: M6.43 — reader分段流式读取(Phase1,只txt) + 面板前台管理/返回回桌面按钮
- reader分段(只txt): core/seg.{h,c} 512KB码位对齐切段(尾字完整)+manifest+按需读单段; book.c rd_book_open只布局每页所在段(治CPU100%/返回无效) load_seg只读当前段(非txt原路径); reader.c 进度实时保存(翻页/刮擦/字号/编码dirty+on_tick节流落盘+退出即时写,锚点存段号,崩溃不丢进度); rdtxt.c fread替代fgetc(去开书扫描卡顿); Phase2(双段拼接+4%释放)留待
- 面板: /api/ams/bg加fg/fg_count(后台保持向后兼容)+POST /api/ams/:pkg/killfg(前台救命:优先am kill,launcher拒则pid直信号SIGTERM->SIGKILL绕过门控)+前端/#/bg前台组'终止'绿徽标+确认弹窗(一键清仍只后台不误杀前台); POST /api/input/gesture(返回=左缘右滑/回桌面=底部上滑,注入滑动轨迹复用SDK手势)+前端屏幕控制页加返回/回桌面MD3按钮

0warning;宿主test_reader_core 72-0/seg_smoke 19-0/loaders_smoke/book_smoke/formats_smoke PASS;面板selftest/node check PASS
2026-11-04 19:47:10 +08:00

399 lines
18 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.
/* mp_screen.c — 实时画面域:/api/screen.jpeg(fb0→JPEG,增量帧)+ 触摸注入 /api/input/tap
* 与系统手势注入 /api/input/gesture(back=左缘右滑 / home=底部上滑回桌面)。 */
#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
/* 手势注入阈值(镜像 meow_ui_input.c / np_gesture.h 系统已识别手势,勿新造语义):
* 返回=左缘(起)右移 ≥SWIPE_THRESH 且横向主导;回桌面=底部热区(起)上移 ≥HOME_SWIPE
* 且竖向主导 + down_x≥EDGE_W(避左缘返回列)。此处位移留容差(≥65)。 */
#define GESTURE_EDGE_W 24 /* 左缘宽度:返回手势起点 x 须 < 此值(meow_ui_input.c EDGE_W) */
#define GESTURE_SWIPE_DX 78 /* 返回手势右移位移(≥ SDK SWIPE_THRESH 65,留容差) */
#define GESTURE_HOME_EDGE_H 28 /* 底部热区高:回桌面起点 y 须 >= fb_h-此值(HOME_EDGE_H) */
#define GESTURE_HOME_DY 80 /* 回桌面手势上移位移(≥ SDK HOME_SWIPE 65,留容差) */
#define GESTURE_STEPS 10 /* 线性轨迹插值点数(营造真实滑动轨迹) */
#define GESTURE_STEP_MS 12 /* 每步间隔毫秒 */
#define GESTURE_HOLD_MS 60 /* 末端按住毫秒再抬起(防 fling 误判) */
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;
}
/* 线性滑动注入:按下(原点) → 插值移动 → 末端按住 → 抬起。事件序同 inject_tap
* (BTN_TOUCH 边沿先于 ABS,goodix 时序免疫;首帧锁原点),设备手势机据此判
* 「左缘右滑=返回」或「底部上滑=回桌面」(复用系统已识别信号,不新造)。 */
int inject_swipe(const char *dev, int x0, int y0, int x1, int y1)
{
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)
/* 按下边沿 + 首帧 ABS 锁原点(与 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, x0);
__WE(EV_ABS, ABS_MT_POSITION_Y, y0);
__WE(EV_ABS, ABS_MT_TOUCH_MAJOR, 6);
__WE(EV_ABS, ABS_X, x0);
__WE(EV_ABS, ABS_Y, y0);
__WE(EV_SYN, SYN_REPORT, 0);
/* 插值逐点移动:每步坐标更新 + 同步,营造连续轨迹 */
for (int i = 1; i <= GESTURE_STEPS; i++) {
int xi = x0 + (x1 - x0) * i / GESTURE_STEPS;
int yi = y0 + (y1 - y0) * i / GESTURE_STEPS;
__WE(EV_ABS, ABS_MT_POSITION_X, xi);
__WE(EV_ABS, ABS_MT_POSITION_Y, yi);
__WE(EV_ABS, ABS_X, xi);
__WE(EV_ABS, ABS_Y, yi);
__WE(EV_SYN, SYN_REPORT, 0);
usleep((useconds_t)GESTURE_STEP_MS * 1000);
}
/* 末端短暂按住再抬起(与 tap 同:抬起到按下边沿被手势机视为一次抬手) */
usleep((useconds_t)GESTURE_HOLD_MS * 1000);
__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;
}
/* /api/input/gesture:注入设备已识别的系统手势(back=左缘右滑 / home=底部上滑回桌面)。
* 复用现有触摸注入通道(/dev/input/event1 滑动轨迹),不触碰被物理键红线禁注入的
* /api/key 158/172;由设备自身手势机判成 MEOW_SWIPE_LEFT / MEOW_EV_HOME。 */
int api_input_gesture(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 *jg = jv_obj_get(root, "gesture");
if (!jg || jg->type != J_STR) {
jv_free(root);
return send_err(sock, 400, "BAD_REQUEST", "缺 gesture(back|home)");
}
const char *gesture = jg->str;
int is_back = (strcmp(gesture, "back") == 0);
int is_home = (strcmp(gesture, "home") == 0);
jv_free(root);
if (!is_back && !is_home)
return send_err(sock, 400, "BAD_REQUEST", "gesture 非法(仅 back|home)");
const char *dev = getenv("MEOW_TOUCH_DEV"); if (!dev || !dev[0]) dev = TOUCH_DEV;
const char *fbdev = getenv("MEOW_FB_DEV"); if (!fbdev || !fbdev[0]) fbdev = FB_DEV;
int sw = 480, sh = 720;
{ int fx = 0, fy = 0; if (fb_dims(fbdev, &fx, &fy) == 0) { sw = fx; sh = fy; } }
if (sw < 1) sw = 480;
if (sh < 1) sh = 720;
/* 几何(镜像 SDK 判据 + 留容差,见上 GESTURE_* 宏) */
int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
if (is_back) {
x0 = 4; /* 左缘内(< EDGE_W=24) */
if (x0 >= sw) x0 = sw - 1;
if (x0 >= GESTURE_EDGE_W) x0 = GESTURE_EDGE_W - 1;
y0 = y1 = sh / 2; /* 屏中部,避开顶部下拉/底部上滑热区 */
x1 = x0 + GESTURE_SWIPE_DX;
if (x1 >= sw) x1 = sw - 1; /* 横向主导 + 右移≥阈值 */
} else { /* home */
x0 = x1 = sw / 2; /* 屏底中部,避开左缘返回列 */
if (x0 < GESTURE_EDGE_W) x0 = x1 = GESTURE_EDGE_W;
y0 = sh - 2; /* 底部热区(>= fb_h-HOME_EDGE_H) */
if (y0 >= sh) y0 = sh - 1;
y1 = y0 - GESTURE_HOME_DY;
if (y1 < 0) y1 = 0; /* 竖向主导 + 上移≥阈值 */
}
/* 仲裁:本机最近触摸后 5s 内忽略注入(与 tap 同) */
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_swipe(dev, x0, y0, x1, y1) < 0)
return send_err(sock, 500, "INTERNAL", "手势注入失败(无法打开触摸设备)");
sbuf_t s = {0};
sA(&s, "{\"dispatched\":true,\"gesture\":\"%s\",\"from\":[%d,%d],\"to\":[%d,%d]}",
gesture, x0, y0, x1, y1);
int ret = send_json(sock, 200, s.b ? s.b : "");
sbuf_free(&s);
return ret;
}