feat(meowpanel): 通知实时化+屏幕增量帧 (agent-234) — /api/notifications 优先 IPC 读 launcher 活跃集(source:ipc,超时回退TF), dismiss/clear 联动 launcher 内存; /api/screen.jpeg?delta=1 8x8块比对+变化块外接矩形子图(近静态27KB→804B), 前端canvas叠加+开关
meowpanel→24f53b40 已部署真机验证(通知实时出现/撤下联动/增量帧带宽实测)
This commit is contained in:
1 parent
e9b6b690c1
commit
829ac3ba88
6 files changed
+421
-61
No files matched your search
+266
-17
@@ -89,6 +89,7 @@ static const char *g_notif_log = NULL; /* 通知转储日志:<card>/notificat
|
||||
#define LOGS_DIR g_logs_dir
|
||||
#define PERF_SOCK "/usr/data/meow_perf.sock"
|
||||
#define POWER_SOCK "/usr/data/meow_power.sock"
|
||||
#define LAUNCHER_SOCK "/usr/data/meow_ipc.sock" /* launcher 主 IPC(通知活跃集查询/撤清) */
|
||||
#define NYAI_PATH "/usr/bin/nyai" /* 固件内置(权威) */
|
||||
#define NYAI_PATH_CARD "/usr/data/mnt/sd_0/MeowOS/opt/bin/nyai" /* 旧卡上路径(回退) */
|
||||
#define BOOT_DEFAULT_FILE "/data/meow_boot_default"
|
||||
@@ -118,6 +119,14 @@ static const char *g_sensitive_names[] = {
|
||||
|
||||
static const char *g_sock_perf = PERF_SOCK;
|
||||
static const char *g_sock_power = POWER_SOCK;
|
||||
static const char *g_sock_launcher = LAUNCHER_SOCK;
|
||||
|
||||
/* ---------------- 增量帧缓存(GET /api/screen.jpeg?delta=1,阶段 C 增强) ----------------
|
||||
* 服务端缓存上一帧紧凑 RGB565,按 8×8 块比对,只编码变化块(外接 bbox 子图)并以响应头回传偏移。
|
||||
* 单线程 accept 循环持有,跨请求持久(见 §5.10)。 */
|
||||
#define SCREEN_BLOCK 8 /* 比对块边长(像素) */
|
||||
static uint8_t *g_screen_prev = NULL; /* 上一帧紧凑 RGB565(w*2 行距) */
|
||||
static int g_screen_pw = 0, g_screen_ph = 0;
|
||||
|
||||
/* 初始化 TF 卡派生路径(main 开头调用)。env MEOW_TF_ROOT 可覆盖卡根(宿主测试用);
|
||||
* env MEOW_NOTIF_LOG 可覆盖通知转储日志路径(宿主测试用)。默认设备卡路径。 */
|
||||
@@ -2791,20 +2800,20 @@ static int api_logs_read(int sock, const req_t *r)
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* ---------------- 通知(数据源 = TF 卡通知转储日志) ----------------
|
||||
/* ---------------- 通知(数据源 = 优先 launcher 内存活跃集,回退 TF 归档) ----------------
|
||||
* 数据源优先级(MEOWPANEL_PROTOCOL §3.8 / 任务):
|
||||
* 1) launcher IPC 查询 —— meow_ipc.h 已定义 MSG_NOTIFY_LIST(0x23),但 launcher 侧仅作
|
||||
* 守护→App 下发行(util.c:1079「not dispatched here」),**无客户端查询请求类型**,
|
||||
* 后端(svcd 独立进程)无法据此取到活跃清单 → 跳过,回退 ②。详见报告「需 launcher 侧配合项」。
|
||||
* 2) TF 卡通知转储文件 `<卡根>/MeowOS/notifications/notifications.log`:
|
||||
* 1) launcher IPC 内存活跃集(实时)—— `MSG_NOTIFY_QUERY`(0x25)→`MSG_NOTIFY_QUERY_RESULT`(0x27)
|
||||
* (launcher `util.c` `case MSG_NOTIFY_QUERY`,**主循环单写者**序列化;agent-199 已落地)。
|
||||
* 2) IPC 不可达/超时/应答失败 → 回退 TF 卡通知转储文件 `<卡根>/MeowOS/notifications/notifications.log`:
|
||||
* launcher 把每条通知归档时追加一行 CSV(meow_notif.c tf_line:CSV 追加 + 10MB 滚动,开机清空)。
|
||||
* 字段顺序:timestamp,id,app_id,app_icon,category,urgency,progress,title,body,target_activity,
|
||||
* ongoing,media,dismissed(前 6/7/11/12/13 为裸整数,其余引号包裹 + 内部 " 翻倍)。
|
||||
* 注意:**归档发生在置 dismissed=1 之前(meow_notif_dismiss 先 archive_one 后置 dismissed)**,
|
||||
* 故日志的 dismissed 列恒 ≈0(不可靠),且日志是「会话级归档」,非活跃快照。
|
||||
* 故日志的 dismissed 列恒 ≈0(不可靠),且日志是「会话级归档」,非活跃快照(回退时为此语义)。
|
||||
* 3) 都没有 → 明确空态 {items:[],notifications:[],source:"none"},**不伪造数据**。
|
||||
* clear(dismiss_all)语义:保留活动中的媒体/进度通知(ongoing||media||progress>=0||category=="media"),
|
||||
* 其余从日志移除。单条删除:按 id 移除该行。 */
|
||||
* 撤/清(delete / dismiss_all)同样**优先走 IPC**联动 launcher 内存(launcher 撤下时自动归档留档),
|
||||
* IPC 不可达时回退重写 TF 日志。clear(dismiss_all)语义:保留活动中的媒体/进度通知
|
||||
* (ongoing||media||progress>=0||category=="media"),其余移除。 */
|
||||
typedef struct {
|
||||
long long ms; /* created_ms(epoch ms) */
|
||||
unsigned id;
|
||||
@@ -2935,11 +2944,124 @@ static void notif_emit_item(sbuf_t *out, const notif_rec_t *r)
|
||||
sA(out, ",\"dismissed\":%s}", r->dismissed ? "true" : "false");
|
||||
}
|
||||
|
||||
/* ---------------- 通知实时化:launcher IPC 活跃集 ----------------
|
||||
* 数据源优先级(MEOWPANEL_PROTOCOL §3.8 / 任务):
|
||||
* 1) launcher IPC 内存活跃集(实时)—— `MSG_NOTIFY_QUERY`(0x25)→`MSG_NOTIFY_QUERY_RESULT`(0x27),
|
||||
* JSON 字段同 TF 归档(id/app_id/category/summary/body/urgency/ts(ms)/progress/ongoing/media/
|
||||
* target_activity/app_icon)。launcher 在**主循环单写者**序列化(绝不把通知存储读到 IPC 线程)。
|
||||
* 2) IPC 不可达/超时/应答失败 → 回退 TF 归档 `notifications.log`(保留旧行为,见 §5.8)。
|
||||
* 3) 都没有 → 明确空态 {items:[],notifications:[],source:"none"},不伪造数据。
|
||||
* 撤/清(delete / dismiss_all)同样**优先走 IPC**联动 launcher 内存(launcher 撤下时自动归档留档),
|
||||
* IPC 不可达时回退重写 TF 日志。 */
|
||||
|
||||
/* 发起 MSG_NOTIFY_QUERY(空 payload=全部 app),把 launcher 返回的 JSON 数组解析为 recs[]。
|
||||
* 返回条数(>=0,按 ms 升序 oldest..newest,与 TF 路径一致);-1=IPC 不可达/应答失败/解析失败。
|
||||
* 注:launcher 按固定 slot 表顺序序列化,非时间序,故此处按 ms 升序重排后再返回。 */
|
||||
static int notif_ipc_query(notif_rec_t *recs, int maxrec)
|
||||
{
|
||||
int fd = ipc_connect_tmo(g_sock_launcher, IPC_TIMEOUT_MS);
|
||||
if (fd < 0) return -1;
|
||||
int cnt = -1;
|
||||
if (ipc_hello(fd) < 0) goto done;
|
||||
if (send_frame(fd, MSG_NOTIFY_QUERY, 8, NULL, 0) < 0) goto done;
|
||||
uint8_t buf[MEOW_IPC_MAX + MEOW_IPC_HDR_LEN];
|
||||
uint16_t type; uint32_t rid;
|
||||
int n = recv_frame(fd, &type, &rid, buf, sizeof buf);
|
||||
if (n < (int)sizeof(m_notify_query_result_t)) goto done;
|
||||
if (type != MSG_NOTIFY_QUERY_RESULT) goto done;
|
||||
m_notify_query_result_t qr;
|
||||
memcpy(&qr, buf, sizeof qr);
|
||||
if (qr.code != MEOW_OK) goto done;
|
||||
uint16_t tl = qr.text_len;
|
||||
if (tl >= sizeof qr.text) tl = (uint16_t)(sizeof qr.text - 1);
|
||||
if (tl == 0) { cnt = 0; goto done; } /* 无活跃通知 */
|
||||
jv_t *arr = jv_parse(qr.text, tl);
|
||||
if (!arr) goto done;
|
||||
if (arr->type != J_ARR) { jv_free(arr); goto done; }
|
||||
int c = 0;
|
||||
for (jv_t *e = arr->child; e && c < maxrec; e = e->next) {
|
||||
if (e->type != J_OBJ) continue;
|
||||
notif_rec_t r; memset(&r, 0, sizeof r);
|
||||
r.progress = -1;
|
||||
r.urgency = 0;
|
||||
jv_t *v;
|
||||
if ((v = jv_obj_get(e, "id")) && v->type == J_NUM) r.id = (unsigned)v->inum;
|
||||
if ((v = jv_obj_get(e, "app_id")) && v->type == J_STR) snprintf(r.app_id, sizeof r.app_id, "%s", v->str);
|
||||
if ((v = jv_obj_get(e, "app_icon")) && v->type == J_STR) snprintf(r.app_icon, sizeof r.app_icon, "%s", v->str);
|
||||
if ((v = jv_obj_get(e, "category")) && v->type == J_STR) snprintf(r.category, sizeof r.category, "%s", v->str);
|
||||
if ((v = jv_obj_get(e, "summary")) && v->type == J_STR) snprintf(r.summary, sizeof r.summary, "%s", v->str);
|
||||
if ((v = jv_obj_get(e, "body")) && v->type == J_STR) snprintf(r.body, sizeof r.body, "%s", v->str);
|
||||
if ((v = jv_obj_get(e, "target_activity")) && v->type == J_STR) snprintf(r.target, sizeof r.target, "%s", v->str);
|
||||
if ((v = jv_obj_get(e, "urgency")) && v->type == J_NUM) r.urgency = (int)v->inum;
|
||||
/* ts 为 epoch 毫秒 → 存入 r.ms(emit 时 /1000 出秒,与 TF 同口径) */
|
||||
if ((v = jv_obj_get(e, "ts")) && v->type == J_NUM) r.ms = (long long)v->inum;
|
||||
if ((v = jv_obj_get(e, "progress")) && v->type == J_NUM) r.progress = (int)v->inum; /* <0=无;json 无该键则 -1 */
|
||||
if ((v = jv_obj_get(e, "ongoing")) && v->type == J_NUM) r.ongoing = (int)v->inum;
|
||||
if ((v = jv_obj_get(e, "media")) && v->type == J_NUM) r.media = (int)v->inum;
|
||||
recs[c++] = r;
|
||||
}
|
||||
jv_free(arr);
|
||||
/* 按 ms 升序(oldest..newest),与 TF 路径一致(emit 时倒序遍历= newest first)。 */
|
||||
for (int i = 1; i < c; i++) { notif_rec_t key = recs[i]; int j = i - 1;
|
||||
while (j >= 0 && recs[j].ms > key.ms) { recs[j + 1] = recs[j]; j--; } recs[j + 1] = key; }
|
||||
cnt = c;
|
||||
done:
|
||||
close(fd);
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/* 撤下单条通知 → MSG_NOTIFY_DISMISS(0x22),期望 MSG_ACK。返回 0=受理;-1=IPC 不可达/应答失败/未受理。 */
|
||||
static int notif_ipc_dismiss(unsigned id)
|
||||
{
|
||||
int fd = ipc_connect_tmo(g_sock_launcher, IPC_TIMEOUT_MS);
|
||||
if (fd < 0) return -1;
|
||||
int rc = -1;
|
||||
if (ipc_hello(fd) < 0) goto done;
|
||||
uint32_t nid = (uint32_t)id;
|
||||
if (send_frame(fd, MSG_NOTIFY_DISMISS, 9, &nid, sizeof nid) < 0) goto done;
|
||||
uint8_t buf[64];
|
||||
uint16_t type; uint32_t rid;
|
||||
int n = recv_frame(fd, &type, &rid, buf, sizeof buf);
|
||||
if (n < (int)sizeof(m_ack_t)) goto done;
|
||||
if (type != MSG_ACK) goto done;
|
||||
m_ack_t a; memcpy(&a, buf, sizeof a);
|
||||
rc = (a.code == MEOW_OK) ? 0 : -1;
|
||||
done:
|
||||
close(fd);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* 一键清除 → MSG_NOTIFY_DISMISS_ALL(0x26),期望 MSG_ACK(status=清除数,保留 ongoing/media/progress)。
|
||||
* 返回 0=受理(*out_count 填清除数);-1=IPC 不可达/应答失败。 */
|
||||
static int notif_ipc_dismiss_all(int *out_count)
|
||||
{
|
||||
int fd = ipc_connect_tmo(g_sock_launcher, IPC_TIMEOUT_MS);
|
||||
if (fd < 0) return -1;
|
||||
int rc = -1;
|
||||
if (ipc_hello(fd) < 0) goto done;
|
||||
if (send_frame(fd, MSG_NOTIFY_DISMISS_ALL, 10, NULL, 0) < 0) goto done;
|
||||
uint8_t buf[64];
|
||||
uint16_t type; uint32_t rid;
|
||||
int n = recv_frame(fd, &type, &rid, buf, sizeof buf);
|
||||
if (n < (int)sizeof(m_ack_t)) goto done;
|
||||
if (type != MSG_ACK) goto done;
|
||||
m_ack_t a; memcpy(&a, buf, sizeof a);
|
||||
if (a.code == MEOW_OK) { if (out_count) *out_count = (int)a.status; rc = 0; }
|
||||
done:
|
||||
close(fd);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int api_notifications(int sock)
|
||||
{
|
||||
notif_rec_t recs[NOTIF_PREVIEW_MAX];
|
||||
int n = notif_load_list(recs, NOTIF_PREVIEW_MAX);
|
||||
if (n < 0) return send_json(sock, 200, "{\"count\":0,\"items\":[],\"notifications\":[],\"source\":\"none\"}");
|
||||
const char *source = "ipc";
|
||||
int n = notif_ipc_query(recs, NOTIF_PREVIEW_MAX);
|
||||
if (n < 0) { /* IPC 不可达 → 回退 TF 归档 */
|
||||
n = notif_load_list(recs, NOTIF_PREVIEW_MAX);
|
||||
source = (n < 0) ? "none" : "tf";
|
||||
if (n < 0) n = 0;
|
||||
}
|
||||
sbuf_t arr = {0};
|
||||
int first = 1;
|
||||
for (int i = n - 1; i >= 0; i--) { /* newest first */
|
||||
@@ -2948,7 +3070,7 @@ static int api_notifications(int sock)
|
||||
notif_emit_item(&arr, &recs[i]);
|
||||
}
|
||||
sbuf_t s = {0};
|
||||
sA(&s, "{\"count\":%d,\"source\":\"tf\",\"items\":[", n);
|
||||
sA(&s, "{\"count\":%d,\"source\":\"%s\",\"items\":[", n, source);
|
||||
if (arr.n) sA_append(&s, arr.b, arr.n);
|
||||
sA(&s, "],\"notifications\":[");
|
||||
if (arr.n) sA_append(&s, arr.b, arr.n);
|
||||
@@ -2994,6 +3116,12 @@ static int notif_rewrite(int mode, unsigned del_id)
|
||||
|
||||
static int api_notification_delete(int sock, unsigned id)
|
||||
{
|
||||
/* 优先 IPC 联动 launcher 内存(launcher 撤下时自动归档留档);IPC 不可达/未受理回退 TF 重写。 */
|
||||
if (notif_ipc_dismiss(id) == 0) {
|
||||
char d[64];
|
||||
snprintf(d, sizeof d, "{\"id\":%u,\"dismissed\":true}", id);
|
||||
return send_json(sock, 200, d);
|
||||
}
|
||||
int removed = notif_rewrite(0, id);
|
||||
if (removed < 0) return send_err(sock, 500, "INTERNAL", "写通知存储失败");
|
||||
if (removed == 0) return send_err(sock, 404, "NOT_FOUND", "通知不存在");
|
||||
@@ -3004,7 +3132,13 @@ static int api_notification_delete(int sock, unsigned id)
|
||||
|
||||
static int api_notification_dismiss_all(int sock)
|
||||
{
|
||||
int removed = notif_rewrite(1, 0);
|
||||
int cleared = 0;
|
||||
if (notif_ipc_dismiss_all(&cleared) == 0) { /* 联动 launcher 内存,返回清除数 */
|
||||
char d[64];
|
||||
snprintf(d, sizeof d, "{\"dismissed\":%d}", cleared);
|
||||
return send_json(sock, 200, d);
|
||||
}
|
||||
int removed = notif_rewrite(1, 0); /* IPC 不可达 → 回退重写 TF 日志 */
|
||||
if (removed < 0) return send_err(sock, 500, "INTERNAL", "写通知存储失败");
|
||||
char d[64];
|
||||
snprintf(d, sizeof d, "{\"dismissed\":%d}", removed);
|
||||
@@ -3477,6 +3611,54 @@ static int fb_dims(const char *dev, int *w, int *h)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 把压缩帧缓存为「上一帧」基线(几何变化时重分配)。供 delta 模式 8×8 块比对。 */
|
||||
static 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);
|
||||
}
|
||||
|
||||
/* 8×8 块比对:返回变化块数;bbox 为块坐标(bx/by=min 块,bw/bh=块数),像素原点=bx*8/by*8。
|
||||
* changed==0 时 bbox 无效。*total_blocks 返回总块数。 */
|
||||
static 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;
|
||||
}
|
||||
|
||||
static int api_screen_jpeg(int sock, const req_t *r)
|
||||
{
|
||||
int q = 70; /* 默认质量(体积/清晰折中) */
|
||||
@@ -3485,16 +3667,80 @@ static int api_screen_jpeg(int sock, const req_t *r)
|
||||
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(frame, w, h, q, &jpeg, &jlen);
|
||||
free(frame);
|
||||
if (rc != 0) return send_err(sock, 500, "INTERNAL", "JPEG 编码失败");
|
||||
int ret = send_raw(sock, 200, "image/jpeg", jpeg, jlen);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -3811,10 +4057,11 @@ int main(int argc, char **argv)
|
||||
if (!strcmp(argv[i], "-p") && i + 1 < argc) port = atoi(argv[++i]);
|
||||
else if (!strcmp(argv[i], "--sock-perf") && i + 1 < argc) g_sock_perf = argv[++i];
|
||||
else if (!strcmp(argv[i], "--sock-power") && i + 1 < argc) g_sock_power = argv[++i];
|
||||
else if (!strcmp(argv[i], "--sock-launcher") && i + 1 < argc) g_sock_launcher = argv[++i];
|
||||
else if (!strcmp(argv[i], "--sock") && i + 1 < argc) { g_sock_perf = argv[++i]; g_sock_power = g_sock_perf; }
|
||||
else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
|
||||
printf("MeowPanel backend %s\n", PANEL_VERSION);
|
||||
printf("usage: meowpanel [-p PORT] [--sock-perf P] [--sock-power P]\n");
|
||||
printf("usage: meowpanel [-p PORT] [--sock-perf P] [--sock-power P] [--sock-launcher P]\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -3823,6 +4070,8 @@ int main(int argc, char **argv)
|
||||
if (env && env[0]) g_sock_perf = env;
|
||||
env = getenv("MEOW_IPC_POWER_SOCK");
|
||||
if (env && env[0]) g_sock_power = env;
|
||||
env = getenv("MEOW_IPC_LAUNCHER_SOCK");
|
||||
if (env && env[0]) g_sock_launcher = env;
|
||||
env = getenv("MEOW_SETTINGS_FILE");
|
||||
if (env && env[0]) g_settings_file = env; /* 设置文件覆盖(宿主测试用;设备默认 /usr/data/meow/settings.conf) */
|
||||
|
||||
|
||||
Reference in new issue
Block a user