/* 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; }