diff --git a/patch_v2/meow_svcd/meow_svcd b/patch_v2/meow_svcd/meow_svcd index 846cadf..b27e072 100755 Binary files a/patch_v2/meow_svcd/meow_svcd and b/patch_v2/meow_svcd/meow_svcd differ diff --git a/patch_v2/meow_svcd/meow_svcd.c b/patch_v2/meow_svcd/meow_svcd.c index 48cda6b..6d23e31 100644 --- a/patch_v2/meow_svcd/meow_svcd.c +++ b/patch_v2/meow_svcd/meow_svcd.c @@ -27,10 +27,16 @@ * - boot:配置在卡 → 随卡存在即启动(拔卡即停)。 * * 控制通道:AF_UNIX 单行文本协议(默认 /tmp/meow_svcd.sock): - * start|stop|restart / status / reload / pid + * start|stop|restart / status [name|all] / reload / pid * 后台常驻模式默认(无子命令即启动守护进程);`-f/--foreground` 供 supervisor * fork/exec 托管(保证 supervisor 可 waitpid)。 * + * status 回显(详细 + ANSI 配色): + * 每服务列出 状态/pid/重启次数+max_restart/backoff_ms/trigger/来源(板载|TF)/ + * 最近退出码/退出时间/exec 路径;标题+列名+对齐;关键字段着色(RUNNING=绿、 + * FAILED=红、STOPPED·STOPPING=黄、DISABLED=灰、服务名=青、数字=黄、表头=dim)。 + * ANSI 色由守护进程统一产出,客户端在 stdout 非 TTY(重定向/SSH 管道)时去色。 + * * 状态文件:`/tmp/meow_svcd.status`(可 --status 覆盖),状态变更+周期落盘, * 板载 tmpfs 存活期内可读。 * @@ -91,6 +97,16 @@ #define SVCD_SIGCHLD_CHAR 'C' /* 自管道字节(拉起 poll) */ #define SVCD_SIGTERM_CHAR 'T' +/* ---- ANSI 终端配色(status 回显用;仅客户端 stdout 为 TTY 时保留,非 TTY 去色) ---- */ +#define AN_RESET "\033[0m" +#define AN_BOLD "\033[1m" +#define AN_DIM "\033[2m" +#define AN_RED "\033[31m" +#define AN_GREEN "\033[32m" +#define AN_YELLOW "\033[33m" +#define AN_CYAN "\033[36m" +#define AN_GREY "\033[90m" + /* ---- 服务状态机 ---- */ typedef enum { SV_DISABLED = 0, /* enabled=0,永不自动启动 */ @@ -124,6 +140,8 @@ typedef struct { sv_state_t state; int restart_count; int last_exit_status; /* waitpid status */ + int has_exit; /* 1=曾退出(last_exit_* 有效) */ + unsigned long long last_exit_wall; /* 上次退出墙钟秒(0=未知;仅供 status 展示) */ unsigned long long restart_at_ms; /* 退避到期时间;0=无到期 */ unsigned long long stop_since_ms; /* SIGTERM 发出时刻 */ unsigned long long run_since_ms; /* 本轮启动时刻 */ @@ -433,6 +451,8 @@ static void apply_exit(svc_t *s, int status) int failed = !(WIFEXITED(status) && WEXITSTATUS(status) == 0); s->pid = 0; s->last_exit_status = status; + s->has_exit = 1; + s->last_exit_wall = (unsigned long long)time(NULL); if (s->restart_pending) { /* restart 命令:停后原地重启,无视崩溃策略 */ @@ -680,6 +700,8 @@ static void scan_and_reconcile(void) /* 保留运行期字段,其余用 tmp 覆盖 */ int keep_count = s->restart_count; int keep_exit = s->last_exit_status; + int keep_has = s->has_exit; + unsigned long long keep_wall = s->last_exit_wall; pid_t keep_pid = s->pid; int keep_stop = s->stop_requested; int keep_rp = s->restart_pending; @@ -688,6 +710,8 @@ static void scan_and_reconcile(void) *s = tmp; s->restart_count = keep_count; s->last_exit_status = keep_exit; + s->has_exit = keep_has; + s->last_exit_wall = keep_wall; s->pid = keep_pid; s->stop_requested = keep_stop; s->restart_pending = keep_rp; @@ -841,22 +865,153 @@ static int cmd_restart(const char *name) return 0; } -static void cmd_status(int fd) +/* ---- status 回显(详细 + ANSI 配色;非 TTY 由客户端去色) ---- */ + +/* 追加一个字段:可选上色(color 包裹 plain + 复位),再按纯文本宽度补空格对齐。 + * width<0 表示最后一列(不补齐)。b 为累积缓冲,*o 为偏移。 */ +static void put_field(char *b, size_t cap, size_t *o, const char *color, const char *plain, int width) { - send_resp(fd, "# meow_svcd status\n"); - char line[384]; - int k = snprintf(line, sizeof line, "card_present=%d wifi_up=%d services=%d\n", - g_card_present, g_wifi_up, g_nsvc); - send_resp(fd, line); - (void)k; - for (int i = 0; i < g_nsvc; i++) { - svc_t *s = &g_svcs[i]; - snprintf(line, sizeof line, - "svc=%.63s state=%.16s pid=%d restart=%d exit=%d trigger=%.15s\n", - s->name, g_state_str[s->state], (int)s->pid, s->restart_count, - s->last_exit_status, s->trigger); - send_resp(fd, line); + size_t pl = strlen(plain); + int w; + if (color && color[0]) + w = snprintf(b + *o, cap - *o, "%s%s" AN_RESET, color, plain); + else + w = snprintf(b + *o, cap - *o, "%s", plain); + if (w > 0) *o += (size_t)w; + if (*o >= cap) *o = cap - 1; /* 截断保护:o 永不超过 cap-1 */ + if (width > 0) { + int pad = width - (int)pl; + while (pad-- > 0 && *o < cap - 1) b[(*o)++] = ' '; } + b[*o] = '\0'; +} + +static void put_str(char *b, size_t cap, size_t *o, const char *s) +{ + int w = snprintf(b + *o, cap - *o, "%s", s); + if (w > 0) *o += (size_t)w; + if (*o >= cap) *o = cap - 1; + b[*o] = '\0'; +} + +/* 状态 → 关键色:RUNNING=绿 / FAILED=红 / STOPPED·STOPPING=黄 / DISABLED=灰。 */ +static const char *state_color(int st) +{ + switch (st) { + case SV_RUNNING: return AN_GREEN; + case SV_FAILED: return AN_RED; + case SV_STOPPED: return AN_YELLOW; + case SV_STOPPING: return AN_YELLOW; + case SV_DISABLED: return AN_GREY; + default: return ""; + } +} + +/* 解码 waitpid status 为短串:exit=N / sig=N / "-"(未曾退出)。 */ +static void fmt_exit_str(const svc_t *s, char *out, size_t cap) +{ + if (!s->has_exit) { snprintf(out, cap, "-"); return; } + int st = s->last_exit_status; + if (WIFEXITED(st)) snprintf(out, cap, "%d", WEXITSTATUS(st)); + else if (WIFSIGNALED(st)) snprintf(out, cap, "sig%d", WTERMSIG(st)); + else snprintf(out, cap, "-"); +} + +/* 上次退出时间 HH:MM:SS;无则 "-"。 */ +static void fmt_exit_time(const svc_t *s, char *out, size_t cap) +{ + if (!s->has_exit || s->last_exit_wall == 0) { snprintf(out, cap, "-"); return; } + time_t t = (time_t)s->last_exit_wall; + struct tm tm; + if (!localtime_r(&t, &tm)) { snprintf(out, cap, "-"); return; } + strftime(out, cap, "%H:%M:%S", &tm); +} + +/* 回显状态:`status`(全部+全局)或 `status `(单服务)。 */ +static void cmd_status(int fd, const char *name) +{ + int idx = -1; /* -1=全部 */ + if (name && strcmp(name, "all") != 0) { + idx = find_svc(name); + if (idx < 0) { send_resp(fd, "ERR no-such-service\n"); return; } + } + int shown = (idx >= 0) ? 1 : g_nsvc; + + /* NAME 列宽(按纯文本计) */ + int nw = 4; /* 至少等于 "NAME" */ + for (int i = 0; i < g_nsvc; i++) { + if (idx >= 0 && i != idx) continue; + int l = (int)strlen(g_svcs[i].name); + if (l > nw) nw = l; + } + if (nw > 40) nw = 40; /* 防超宽 */ + + const int sw = 8, pw = 7, rw = 7, bw = 8, tw = 11, scw = 5, ew = 6, etw = 9; + size_t cap = (size_t)(shown + 3) * 512 + 1024; + char *b = malloc(cap); + if (!b) { send_resp(fd, "ERR out-of-memory\n"); return; } + size_t o = 0; + + /* 标题 + 全局汇总(services= 恒为已知总数) */ + put_field(b, cap, &o, AN_BOLD AN_CYAN, "meow_svcd status", -1); + put_str(b, cap, &o, "\n"); + char hdr[64]; + snprintf(hdr, sizeof hdr, "card_present=%d wifi_up=%d services=%d\n", + g_card_present, g_wifi_up, g_nsvc); + put_str(b, cap, &o, hdr); + + /* 表头(dim) */ + put_field(b, cap, &o, AN_DIM, "NAME", nw); put_str(b, cap, &o, " "); + put_field(b, cap, &o, AN_DIM, "STATE", sw); put_str(b, cap, &o, " "); + put_field(b, cap, &o, AN_DIM, "PID", pw); put_str(b, cap, &o, " "); + put_field(b, cap, &o, AN_DIM, "RESTART", rw); put_str(b, cap, &o, " "); + put_field(b, cap, &o, AN_DIM, "BACKOFF", bw); put_str(b, cap, &o, " "); + put_field(b, cap, &o, AN_DIM, "TRIGGER", tw); put_str(b, cap, &o, " "); + put_field(b, cap, &o, AN_DIM, "SRC", scw); put_str(b, cap, &o, " "); + put_field(b, cap, &o, AN_DIM, "EXIT", ew); put_str(b, cap, &o, " "); + put_field(b, cap, &o, AN_DIM, "EXIT_TIME", etw); put_str(b, cap, &o, " "); + put_field(b, cap, &o, AN_DIM, "EXEC", -1); + put_str(b, cap, &o, "\n"); + + /* 分隔线(dim) */ + int total = nw + 1 + sw + 1 + pw + 1 + rw + 1 + bw + 1 + tw + 1 + scw + 1 + ew + 1 + etw + 1 + 4; + put_str(b, cap, &o, AN_DIM); + for (int x = 0; x < total && o < cap - 1; x++) b[o++] = '-'; + put_str(b, cap, &o, AN_RESET "\n"); + + /* 服务行 */ + for (int i = 0; i < g_nsvc; i++) { + if (idx >= 0 && i != idx) continue; + svc_t *s = &g_svcs[i]; + char pidstr[16], rstr[16], bstr[16], estr[16], etstr[16]; + snprintf(pidstr, sizeof pidstr, "%d", (int)s->pid); + if (s->max_restart < 0) snprintf(rstr, sizeof rstr, "%d/inf", s->restart_count); + else snprintf(rstr, sizeof rstr, "%d/%d", s->restart_count, s->max_restart); + snprintf(bstr, sizeof bstr, "%ldms", s->backoff_ms); + fmt_exit_str(s, estr, sizeof estr); + fmt_exit_time(s, etstr, sizeof etstr); + + put_field(b, cap, &o, AN_BOLD AN_CYAN, s->name, nw); put_str(b, cap, &o, " "); + put_field(b, cap, &o, state_color(s->state), g_state_str[s->state], sw); put_str(b, cap, &o, " "); + put_field(b, cap, &o, AN_YELLOW, pidstr, pw); put_str(b, cap, &o, " "); + put_field(b, cap, &o, AN_YELLOW, rstr, rw); put_str(b, cap, &o, " "); + put_field(b, cap, &o, AN_YELLOW, bstr, bw); put_str(b, cap, &o, " "); + put_field(b, cap, &o, "", s->trigger, tw); put_str(b, cap, &o, " "); + put_field(b, cap, &o, "", (s->src == 1) ? "board" : "tf", scw); put_str(b, cap, &o, " "); + put_field(b, cap, &o, "", estr, ew); put_str(b, cap, &o, " "); + put_field(b, cap, &o, "", etstr, etw); put_str(b, cap, &o, " "); + put_field(b, cap, &o, "", s->exec, -1); + put_str(b, cap, &o, "\n"); + } + + /* 分块写回(避免单次大 write 阻塞/截断) */ + size_t sent = 0; + while (sent < o) { + ssize_t w = write(fd, b + sent, o - sent); + if (w <= 0) break; + sent += (size_t)w; + } + free(b); } /* 读一条命令并分发。返回响应内容(静态/栈缓冲由调用方使用)。 */ @@ -874,7 +1029,7 @@ static void handle_connection(int fd) if (strcmp(line, "pid") == 0) { char r[64]; snprintf(r, sizeof r, "%d\n", (int)getpid()); send_resp(fd, r); } else if (strcmp(line, "status") == 0) { - cmd_status(fd); + cmd_status(fd, sp); /* sp=NULL=全部;=单服务 */ } else if (strcmp(line, "reload") == 0) { scan_and_reconcile(); send_resp(fd, "OK\n"); @@ -1069,6 +1224,24 @@ static int start_daemon(void) } /* ---- CLI ---- */ + +/* 就地去除 ANSI 转义序列(\033[...);用于 stdout 非 TTY 时去色。 */ +static char *strip_ansi(char *s) +{ + char *r = s, *w = s; + while (*r) { + if (*r == '\033' && r[1] == '[') { + r += 2; + while (*r && !(*r >= '@' && *r <= '~')) r++; /* 跳过参数,找到终结字节 */ + if (*r) r++; /* 跳过终结字节 */ + } else { + *w++ = *r++; + } + } + *w = '\0'; + return s; +} + static int run_client(const char *cmdline) { int fd = socket(AF_UNIX, SOCK_STREAM, 0); @@ -1089,13 +1262,21 @@ static int run_client(const char *cmdline) ssize_t w = write(fd, cmdline, strlen(cmdline)); (void)w; shutdown(fd, SHUT_WR); - char rbuf[8192]; + + /* 累积完整响应(可能含 ANSI 色码),再按 stdout 是否 TTY 决定去色:非 TTY/重定向去色。 */ + size_t cap = 65536, len = 0; + char *rbuf = malloc(cap); + if (!rbuf) { close(fd); return 2; } ssize_t n; - while ((n = read(fd, rbuf, sizeof rbuf - 1)) > 0) { - rbuf[n] = '\0'; - fputs(rbuf, stdout); + while ((n = read(fd, rbuf + len, cap - len - 1)) > 0) { + len += (size_t)n; + if (len >= cap - 1) break; /* 响应超缓冲上限,截断兜底 */ } close(fd); + rbuf[len] = '\0'; + if (!isatty(STDOUT_FILENO)) strip_ansi(rbuf); + fputs(rbuf, stdout); + free(rbuf); return 0; } @@ -1104,7 +1285,7 @@ static void usage(const char *argv0) fprintf(stderr, "usage: %s [options] [command]\n" " commands: start | stop | restart \n" - " | status | reload | pid\n" + " | status [name|all] | reload | pid\n" " no command -> start daemon (default; -f/--foreground keeps fg for supervisor)\n" " options:\n" " -f, --foreground stay in foreground (supervisor fork/exec 托管)\n" diff --git a/patch_v2/meow_svcd/meow_svcd.md b/patch_v2/meow_svcd/meow_svcd.md index a5b0e07..e2b0e5a 100644 --- a/patch_v2/meow_svcd/meow_svcd.md +++ b/patch_v2/meow_svcd/meow_svcd.md @@ -148,7 +148,7 @@ FAILED 崩溃且不再重启(超 max_restart / restart=never) AF_UNIX 单行文本: ``` -meow_svcd status # 列出(daemon 未运行则打印状态文件) +meow_svcd status [name|all] # 列出各服务+全局(daemon 未运行则打印状态文件) meow_svcd reload # 重读卡配置 meow_svcd start meow_svcd stop @@ -156,19 +156,26 @@ meow_svcd restart meow_svcd pid ``` -应答:`OK` / `ERR no-such-service` / 多行 `svc=... state=... pid=... restart=... exit=...`。 +应答:`OK` / `ERR no-such-service` / 多列对齐表格。每服务一行:服务名/状态/pid/ +重启次数+`max_restart`/`backoff_ms`/`trigger`/来源(`board`|`tf`)/最近退出码/退出时间/`exec` 路径; +TTY 时带 ANSI 配色(服务名=加粗青、RUNNING=绿、FAILED=红、STOPPED·STOPPING=黄、DISABLED=灰、 +数字=黄、表头/分隔线=dim),stdout 非 TTY(重定向/SSH 管道)自动去色。 ### 6.1 `systemctl` 式 CLI 用法(SSH 终端示例) 守护已由固件 `etc/init.d/S89meow_svcd` 开机自启,SSH 登录设备后**直接敲**命令即可,无需先知道 socket: ```bash -meow_svcd status # 列出所有服务 + 全局状态 -# # meow_svcd status +meow_svcd status # 列出所有服务 + 全局状态(TTY 带色;管道/重定向自动去色) +# meow_svcd status # card_present=1 wifi_up=0 services=3 -# svc=mdnsd state=running pid=1234 restart=0 exit=0 trigger=boot -# svc=meow_netd state=stopped pid=0 restart=0 exit=0 trigger=card-mount -# svc=ntpc state=stopped pid=0 restart=0 exit=0 trigger=card-mount +# NAME STATE PID RESTART BACKOFF TRIGGER SRC EXIT EXIT_TIME EXEC +# ------------------------------------------------------------------------------ +# mdnsd running 1234 0/5 2000ms boot board - - /usr/bin/meow_mdns +# meow_netd stopped 0 0/5 1000ms card-mount tf - - /usr/bin/meow_netd +# ntpc disabled 0 0/inf 1000ms card-mount tf - - /usr/bin/ntpc + +meow_svcd status mdnsd # 只显示单个服务(其余列同;`status all` 等效于无参) meow_svcd restart mdnsd # 重启单个服务(如板载 mDNS 应答器) meow_svcd start meow_netd # 启动单个服务 @@ -181,8 +188,8 @@ meow_svcd pid # 打印守护进程 pid 守护**未运行**时命令回退打印状态文件 `/tmp/meow_svcd.status`(或 `card_present=0 services=0`)并以退出码 1 返回。 - **守护前台**:`-f`/`--foreground` —— 守护自身跑前台,供 supervisor `fork/exec`+`waitpid` 托管; 默认(无子命令)则为后台常驻。 -- 应答:成功 `OK`(`pid`→pid 数字);服务不存在 `ERR no-such-service`;`svc=... state=...` 行 - 含 `pid/restart(重启计数)/exit(上次退出码)/trigger`。 +- 应答:成功 `OK`(`pid`→pid 数字);服务不存在 `ERR no-such-service`;`status` 返回对齐表格, + 行内含 `state/pid/restart(重启计数)/exit(上次退出码)/trigger/来源/backoff_ms/exec`。 ## 7. 编译 diff --git a/patch_v2/sdk/meow_ui_gfx.c b/patch_v2/sdk/meow_ui_gfx.c index e26f796..a31e971 100644 --- a/patch_v2/sdk/meow_ui_gfx.c +++ b/patch_v2/sdk/meow_ui_gfx.c @@ -353,6 +353,49 @@ void meow_ui_fill_rect_alpha(int x, int y, int w, int h, uint32_t color, int alp #define MUI_HINT_H 5 static int g_hint_last = 0; /* 上次画过的条长(清条时只清到它,避免残留) */ +/* 顶部条区「快照恢复」:画条前把 [0..宽)×[hy..hy+MUI_HINT_H) 拍进小缓冲区;回缩/清空时 + * 回填快照还原「真实底色」而非 th->bg —— 全屏页面底=主题 accent,若填 th->bg(黑) 会在 + * accent 底上留下黑色残条(直到下次区域刷新才消失)。快照只在首现条时拍一次(后续 grow + * 不再覆盖快照),每行/全长布局全保留;缓冲为一次性分配(宽×H 行,480×5×2B≈4.8KB)。 */ +static unsigned char *g_hint_snap = NULL; /* 快照(g_line×MUI_HINT_H 字节;NULL=未拍/分配失败) */ +static int g_hint_snap_y = 0; /* 快照行的起始 y(本次手势时的 hy) */ + +/* 拍下「顶部条区」当前 back_buf 内容(只在 g_hint_last==0 的首现条时调用)。 */ +static void hint_snapshot(void) +{ + int hy = meow_ui_statusbar_height(); + if (hy < 0) hy = 0; + g_hint_snap_y = hy; + if (hy + MUI_HINT_H > g_yres) return; /* 越界:放弃(回缩走 bg 兜底) */ + size_t len = (size_t)MUI_HINT_H * (size_t)g_line; + if (!g_hint_snap) { + g_hint_snap = malloc(len); + if (!g_hint_snap) return; + } + memcpy(g_hint_snap, g_fb + (size_t)hy * g_line, len); +} + +/* 从快照回填列区间 [x0,x1) 的 MUI_HINT_H 行(还原条下真实底色)。x0/x1 已夹到屏宽。 */ +static void hint_restore(int x0, int x1) +{ + if (x1 <= x0) return; + if (x0 < 0) x0 = 0; + if (x1 > g_xres) x1 = g_xres; + if (!g_hint_snap) { /* 无快照(分配失败):兜底填背景(旧行为) */ + const meow_theme *th = meow_theme_get(); + meow_ui_fill_rect(x0, g_hint_snap_y, x1 - x0, MUI_HINT_H, th->bg); + return; + } + int bpp = g_bpp / 8; + for (int r = 0; r < MUI_HINT_H; r++) { + size_t row = (size_t)(g_hint_snap_y + r) * g_line; + memcpy(g_fb + row + (size_t)x0 * bpp, + g_hint_snap + (size_t)r * g_line + (size_t)x0 * bpp, + (size_t)(x1 - x0) * bpp); + } + meow_gfx_mark_dirty(x0, g_hint_snap_y, x1 - x0, MUI_HINT_H); /* 标脏:调用方 flush_region 上屏 */ +} + void meow_ui_swipe_hint(int x) { if (!g_init_ok) return; @@ -361,20 +404,19 @@ void meow_ui_swipe_hint(int x) * 有状态栏(非全屏)→ y = 状态栏高度;收合(collapsed)→ y=提示线下缘 * (MUI_SBAR_COLLAPSE_H,与提示线不重叠);无状态栏(全屏/隐藏)→ y=0。 */ int hy = meow_ui_statusbar_height(); - /* 单一实现(与非全屏 Hello 完全一致,全屏仅 hy 不同):grow 画 0..x、shrunk 先清旧长 - * 再画 0..x、x<=0 清空。x<0 视为清空;条长上限=屏宽。 */ + /* 单一实现(与非全屏 Hello 完全一致,全屏仅 hy 不同):grow 画 0..x、shrunk 先把 + * 超出部分还原为快照、x<=0 整条还原。x<0 视为清空;条长上限=屏宽。条色仍取 th->fg。 */ if (x > 0) { if (x > g_xres) x = g_xres; - /* 往回滑(新长度 < 上次)时,先清掉「上次画到旧长度、但本次超出的部分」, - * 否则条只增不减(右滑变长、左滑回缩实时可见)。 */ - if (g_hint_last > x) meow_ui_fill_rect(0, hy, g_hint_last, MUI_HINT_H, th->bg); - /* 条色取主题前景(fg):fg 定义为与背景对比,能同时覆盖「非全屏(底=主题bg)」与 - * 「全屏 Hello 类(底=主题accent)」两种情况——accent 在 Hello 全屏 accent 底上同色不可见。 */ + if (g_hint_last == 0) hint_snapshot(); /* 首现条:拍下未污染区,供此后回填 */ + /* 往回滑(新长度 < 上次)时,把「上次画到旧长度、本次超出的 [x,old)」还原为快照 + * 真实底色(全屏 accent),而非填 th->bg —— 否则左滑回缩在 accent 底上漏黑。 */ + if (g_hint_last > x) hint_restore(x, g_hint_last); meow_ui_fill_rect(0, hy, x, MUI_HINT_H, th->fg); g_hint_last = x; } else if (g_hint_last > 0) { - /* 手指回原点/抬手未达标/抬手达标:清掉此前画的条(填回背景色) */ - meow_ui_fill_rect(0, hy, g_hint_last, MUI_HINT_H, th->bg); + /* 手指回原点/抬手未达标/抬手达标:整条还原为快照真实底色(全屏 accent 上无黑残) */ + hint_restore(0, g_hint_last); g_hint_last = 0; } /* 不发 flush:由调用方 meow_ui_flush_region(0,0,宽,6) 只刷顶部条 */ @@ -832,6 +874,7 @@ void meow_ui_shutdown(void) if (g_fb_fd >= 0) close(g_fb_fd); if (g_back_buf) free(g_back_buf); if (g_dump_path) { free(g_dump_path); g_dump_path = NULL; } + if (g_hint_snap) { free(g_hint_snap); g_hint_snap = NULL; } g_back_buf = NULL; g_fb = NULL; g_fb_mem = NULL; g_fb_fd = -1; g_init_ok = 0; g_mfont_ready = 0; dirty_reset(); diff --git a/patch_v2/sdk/meow_ui_input.c b/patch_v2/sdk/meow_ui_input.c index 1f27d2d..aa19944 100644 --- a/patch_v2/sdk/meow_ui_input.c +++ b/patch_v2/sdk/meow_ui_input.c @@ -125,6 +125,7 @@ static int t_press_valid = 0; /* BTN_TOUCH=1 已见(本次按下的 gate static int t_dx = 0, t_dy = 0; /* 按下原点(BTN_TOUCH 后首个有效 ABS 锁定) */ static int t_cx = 0, t_cy = 0; /* 当前坐标(-1=本次尚未见到有效坐标) */ static int t_origin_valid = 0; /* 原点是否已锁定 */ +static uint64_t t_down_ms = 0; /* 按下时刻(monotonic ms),供实时按住时长用 */ /* 按下边沿:只标记按下,绝不用传入坐标立即锁原点(避免用上一手势残留坐标当原点)。 * t_cx/t_cy 归 -1 —— 本次手势以「无坐标」起步,只接受随后到来的有效 ABS。 */ @@ -136,6 +137,7 @@ static void touch_press(int x, int y) t_origin_valid = 0; t_cx = -1; t_cy = -1; t_dx = 0; t_dy = 0; + t_down_ms = now_ms(); /* 记按下时刻:供实时按住时长/按住视觉 */ } /* 由 ABS_X/ABS_Y 驱动:若本次按下且原点未锁且坐标有效 → 锁原点;再更新当前坐标。 */ static void touch_update(int x, int y) @@ -151,7 +153,7 @@ static void touch_release(void) if (!t_down) return; t_down = 0; t_press_valid = 0; /* 本次按下从未锁到有效坐标(幽灵触摸/坐标未就绪):丢弃,且清空全部轨迹。 */ - if (!t_origin_valid) { t_cx = t_cy = -1; t_dx = t_dy = 0; return; } + if (!t_origin_valid) { t_cx = t_cy = -1; t_dx = t_dy = 0; t_down_ms = 0; return; } t_origin_valid = 0; int mdx = t_cx - t_dx, mdy = t_cy - t_dy; int adx = mdx < 0 ? -mdx : mdx, ady = mdy < 0 ? -mdy : mdy; @@ -192,7 +194,34 @@ static void touch_release(void) push_ev(MEOW_EV_TAP, t_cx, t_cy); } /* 手势级干净起点:清空全部轨迹,下一手势从零开始,杜绝污染无自愈。 */ - t_cx = t_cy = -1; t_dx = t_dy = 0; + t_cx = t_cy = -1; t_dx = t_dy = 0; t_down_ms = 0; +} + +/* ---- 实时触摸态(内部:控件绘制按压/按住视觉用)---- + * 直接读触摸状态机当前值(与语义事件队列无关;无按下/坐标未就绪时 x/y 返回 -1)。 */ +int meow_input_touch_down(void) { return t_down; } +int meow_input_touch_x(void) { return t_cx; } +int meow_input_touch_y(void) { return t_cy; } +unsigned int meow_input_touch_hold_ms(void) +{ + if (!t_down) return 0; + uint64_t e = now_ms() - t_down_ms; + if (e > 0xFFFFFFFFu) e = 0xFFFFFFFFu; + return (unsigned int)e; +} + +/* 宿主单测/内部注入:模拟按压(down=1 时 x,y=压点、hold_ms=已按住时长),设备运行时无调用方。 */ +void meow_input_touch_set(int down, int x, int y, unsigned hold_ms) +{ + if (down) { + t_down = 1; t_press_valid = 1; t_origin_valid = 1; + t_cx = x; t_cy = y; t_dx = x; t_dy = y; + uint64_t now = now_ms(); + t_down_ms = (now > (uint64_t)hold_ms) ? (now - (uint64_t)hold_ms) : 0; + } else { + t_down = 0; t_press_valid = 0; t_origin_valid = 0; + t_cx = -1; t_cy = -1; t_dx = 0; t_dy = 0; t_down_ms = 0; + } } /* ---- 输入线程:poll 所有 event 设备 + 解析 ---- */ diff --git a/patch_v2/sdk/meow_ui_internal.h b/patch_v2/sdk/meow_ui_internal.h index 849c6d2..7767568 100644 --- a/patch_v2/sdk/meow_ui_internal.h +++ b/patch_v2/sdk/meow_ui_internal.h @@ -52,6 +52,18 @@ void meow_input_inject(int type, int a, int b); /* 清空队列(IME 打开前丢弃残留触摸,避免误敲)。 */ void meow_input_drain(void); +/* ---- 实时触摸态(供控件绘制按压/按住视觉;输入线程写、app 主线读)---- + * 与语义事件队列无关:这些直接读输入线程的触摸状态机当前值,让 meow_widget_draw + * 在「手指按下期间」能如实反映按钮的按下/按住视觉,而无需 app 自行追踪按下。 */ +int meow_input_touch_down(void); /* 当前是否有触摸按下 */ +int meow_input_touch_x(void); /* 当前 X(无按下=-1) */ +int meow_input_touch_y(void); /* 当前 Y(无按下=-1) */ +unsigned int meow_input_touch_hold_ms(void); /* 按下已持续毫秒(抬起后 0) */ + +/* 宿主单测/内部注入:模拟一次按压。down=1 时 x,y 为压点、hold_ms 可指定「已按住时长」 + * (把按下时刻回拨),便于测按住态;down=0 复位。设备运行时无调用方。 */ +void meow_input_touch_set(int down, int x, int y, unsigned hold_ms); + /* ---- IME(meow_ui_ime.c)---- */ /* 当前是否显示软键盘。 */ int meow_ime_active(void); diff --git a/patch_v2/sdk/meow_ui_widget.c b/patch_v2/sdk/meow_ui_widget.c index e7d0cac..58c2f4d 100644 --- a/patch_v2/sdk/meow_ui_widget.c +++ b/patch_v2/sdk/meow_ui_widget.c @@ -11,6 +11,20 @@ enum { W_BTN = 0, W_SLIDER = 1, W_SWITCH = 2, W_LIST = 3 }; +/* 按钮「按住」(长按)判定阈值(ms):超过即进入更强态(更深状态层 + 描边环)。 + * 视觉区分「短按按下」与「按住」;触发语义不变(on_tap 仍按抬手判定)。 */ +#define MEOW_BTN_HOLD_MS 500u + +/* MD3 状态层混色:c 为基色、o 为状态色、t 为 o 的占比(0-100)。 + * 用于把主题 accent 向黑/白调出「按下/按住」状态层(日/夜主题各配色)。 */ +static uint32_t blend_color(uint32_t c, uint32_t o, int t) +{ + int rr = (((c >> 16) & 0xFF) * (100 - t) + ((o >> 16) & 0xFF) * t) / 100; + int gg = (((c >> 8) & 0xFF) * (100 - t) + ((o >> 8) & 0xFF) * t) / 100; + int bb = ((c & 0xFF) * (100 - t) + (o & 0xFF) * t) / 100; + return ((uint32_t)rr << 16) | ((uint32_t)gg << 8) | (uint32_t)bb; +} + struct meow_widget { int type; int x, y, w, h; @@ -89,8 +103,31 @@ void meow_widget_draw(meow_widget *w) const meow_theme *th = meow_theme_get(); switch (w->type) { case W_BTN: { - uint32_t bg = w->pressed ? th->dim : th->accent; - meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, bg); + /* 按下/按住 = 视觉态(不改 on_tap/btn_pressed/hit 语义)。按下来源两路: + * - w->pressed:应用在 on_tap 命中后标记(既有语义,保留); + * - 实时触摸命中:SDK 输入层当前正按在该控件内(无需 app 追踪)。 + * 按住=实时触摸命中且持续 ≥MEOW_BTN_HOLD_MS;按下=任一来源。 */ + int live = meow_input_touch_down() && + meow_input_touch_x() >= w->x && meow_input_touch_x() < w->x + w->w && + meow_input_touch_y() >= w->y && meow_input_touch_y() < w->y + w->h; + int press = w->pressed || live; + int hold = live && meow_input_touch_hold_ms() >= MEOW_BTN_HOLD_MS; + if (hold) { + /* 按住:更强态——更深状态层 + 内缩圆角描边环(浅 accent),贴 MD3 强调; + * 动效取舍:状态层按规范即时切换(MD3 ripple 属额外动画,依赖 on_tick 逐帧, + * 非本「加视觉态」范围),不做 1-2 帧插值。 */ + uint32_t hbg = blend_color(th->accent, 0x000000, 24); + meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, hbg); + if (w->w > 6 && w->h > 6) + meow_ui_draw_rect_round(w->x + 1, w->y + 1, w->w - 2, w->h - 2, 9, + blend_color(th->accent, 0xFFFFFF, 45)); + } else if (press) { + /* 按下:轻状态层(accent 向黑调 12%,即变深/偏移),释放恢复;MD3 表面按压。 */ + meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, + blend_color(th->accent, 0x000000, 12)); + } else { + meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, th->accent); + } meow_ui_text_center(w->x + w->w / 2, w->y + (w->h - meow_ui_text_height(th->font_body)) / 2, w->label, th->font_body, 0xFFFFFF); break;