meow_svcd status 美化(117) + 按钮按住/点击样式+滑条黑残快照(118)
This commit is contained in:
1 parent
19a001f007
commit
07c556e907
7 files changed
+352
-43
No files matched your search
Binary file not shown.
+202
-21
@@ -27,10 +27,16 @@
|
|||||||
* - boot:配置在卡 → 随卡存在即启动(拔卡即停)。
|
* - boot:配置在卡 → 随卡存在即启动(拔卡即停)。
|
||||||
*
|
*
|
||||||
* 控制通道:AF_UNIX 单行文本协议(默认 /tmp/meow_svcd.sock):
|
* 控制通道:AF_UNIX 单行文本协议(默认 /tmp/meow_svcd.sock):
|
||||||
* start|stop|restart <name|all> / status / reload / pid
|
* start|stop|restart <name|all> / status [name|all] / reload / pid
|
||||||
* 后台常驻模式默认(无子命令即启动守护进程);`-f/--foreground` 供 supervisor
|
* 后台常驻模式默认(无子命令即启动守护进程);`-f/--foreground` 供 supervisor
|
||||||
* fork/exec 托管(保证 supervisor 可 waitpid)。
|
* 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 覆盖),状态变更+周期落盘,
|
* 状态文件:`/tmp/meow_svcd.status`(可 --status 覆盖),状态变更+周期落盘,
|
||||||
* 板载 tmpfs 存活期内可读。
|
* 板载 tmpfs 存活期内可读。
|
||||||
*
|
*
|
||||||
@@ -91,6 +97,16 @@
|
|||||||
#define SVCD_SIGCHLD_CHAR 'C' /* 自管道字节(拉起 poll) */
|
#define SVCD_SIGCHLD_CHAR 'C' /* 自管道字节(拉起 poll) */
|
||||||
#define SVCD_SIGTERM_CHAR 'T'
|
#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 {
|
typedef enum {
|
||||||
SV_DISABLED = 0, /* enabled=0,永不自动启动 */
|
SV_DISABLED = 0, /* enabled=0,永不自动启动 */
|
||||||
@@ -124,6 +140,8 @@ typedef struct {
|
|||||||
sv_state_t state;
|
sv_state_t state;
|
||||||
int restart_count;
|
int restart_count;
|
||||||
int last_exit_status; /* waitpid status */
|
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 restart_at_ms; /* 退避到期时间;0=无到期 */
|
||||||
unsigned long long stop_since_ms; /* SIGTERM 发出时刻 */
|
unsigned long long stop_since_ms; /* SIGTERM 发出时刻 */
|
||||||
unsigned long long run_since_ms; /* 本轮启动时刻 */
|
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);
|
int failed = !(WIFEXITED(status) && WEXITSTATUS(status) == 0);
|
||||||
s->pid = 0;
|
s->pid = 0;
|
||||||
s->last_exit_status = status;
|
s->last_exit_status = status;
|
||||||
|
s->has_exit = 1;
|
||||||
|
s->last_exit_wall = (unsigned long long)time(NULL);
|
||||||
|
|
||||||
if (s->restart_pending) {
|
if (s->restart_pending) {
|
||||||
/* restart 命令:停后原地重启,无视崩溃策略 */
|
/* restart 命令:停后原地重启,无视崩溃策略 */
|
||||||
@@ -680,6 +700,8 @@ static void scan_and_reconcile(void)
|
|||||||
/* 保留运行期字段,其余用 tmp 覆盖 */
|
/* 保留运行期字段,其余用 tmp 覆盖 */
|
||||||
int keep_count = s->restart_count;
|
int keep_count = s->restart_count;
|
||||||
int keep_exit = s->last_exit_status;
|
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;
|
pid_t keep_pid = s->pid;
|
||||||
int keep_stop = s->stop_requested;
|
int keep_stop = s->stop_requested;
|
||||||
int keep_rp = s->restart_pending;
|
int keep_rp = s->restart_pending;
|
||||||
@@ -688,6 +710,8 @@ static void scan_and_reconcile(void)
|
|||||||
*s = tmp;
|
*s = tmp;
|
||||||
s->restart_count = keep_count;
|
s->restart_count = keep_count;
|
||||||
s->last_exit_status = keep_exit;
|
s->last_exit_status = keep_exit;
|
||||||
|
s->has_exit = keep_has;
|
||||||
|
s->last_exit_wall = keep_wall;
|
||||||
s->pid = keep_pid;
|
s->pid = keep_pid;
|
||||||
s->stop_requested = keep_stop;
|
s->stop_requested = keep_stop;
|
||||||
s->restart_pending = keep_rp;
|
s->restart_pending = keep_rp;
|
||||||
@@ -841,22 +865,153 @@ static int cmd_restart(const char *name)
|
|||||||
return 0;
|
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");
|
size_t pl = strlen(plain);
|
||||||
char line[384];
|
int w;
|
||||||
int k = snprintf(line, sizeof line, "card_present=%d wifi_up=%d services=%d\n",
|
if (color && color[0])
|
||||||
g_card_present, g_wifi_up, g_nsvc);
|
w = snprintf(b + *o, cap - *o, "%s%s" AN_RESET, color, plain);
|
||||||
send_resp(fd, line);
|
else
|
||||||
(void)k;
|
w = snprintf(b + *o, cap - *o, "%s", plain);
|
||||||
for (int i = 0; i < g_nsvc; i++) {
|
if (w > 0) *o += (size_t)w;
|
||||||
svc_t *s = &g_svcs[i];
|
if (*o >= cap) *o = cap - 1; /* 截断保护:o 永不超过 cap-1 */
|
||||||
snprintf(line, sizeof line,
|
if (width > 0) {
|
||||||
"svc=%.63s state=%.16s pid=%d restart=%d exit=%d trigger=%.15s\n",
|
int pad = width - (int)pl;
|
||||||
s->name, g_state_str[s->state], (int)s->pid, s->restart_count,
|
while (pad-- > 0 && *o < cap - 1) b[(*o)++] = ' ';
|
||||||
s->last_exit_status, s->trigger);
|
|
||||||
send_resp(fd, line);
|
|
||||||
}
|
}
|
||||||
|
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 <name|all>`(单服务)。 */
|
||||||
|
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) {
|
if (strcmp(line, "pid") == 0) {
|
||||||
char r[64]; snprintf(r, sizeof r, "%d\n", (int)getpid()); send_resp(fd, r);
|
char r[64]; snprintf(r, sizeof r, "%d\n", (int)getpid()); send_resp(fd, r);
|
||||||
} else if (strcmp(line, "status") == 0) {
|
} else if (strcmp(line, "status") == 0) {
|
||||||
cmd_status(fd);
|
cmd_status(fd, sp); /* sp=NULL=全部;<name|all>=单服务 */
|
||||||
} else if (strcmp(line, "reload") == 0) {
|
} else if (strcmp(line, "reload") == 0) {
|
||||||
scan_and_reconcile();
|
scan_and_reconcile();
|
||||||
send_resp(fd, "OK\n");
|
send_resp(fd, "OK\n");
|
||||||
@@ -1069,6 +1224,24 @@ static int start_daemon(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ---- CLI ---- */
|
/* ---- CLI ---- */
|
||||||
|
|
||||||
|
/* 就地去除 ANSI 转义序列(\033[...<final>);用于 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)
|
static int run_client(const char *cmdline)
|
||||||
{
|
{
|
||||||
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
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));
|
ssize_t w = write(fd, cmdline, strlen(cmdline));
|
||||||
(void)w;
|
(void)w;
|
||||||
shutdown(fd, SHUT_WR);
|
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;
|
ssize_t n;
|
||||||
while ((n = read(fd, rbuf, sizeof rbuf - 1)) > 0) {
|
while ((n = read(fd, rbuf + len, cap - len - 1)) > 0) {
|
||||||
rbuf[n] = '\0';
|
len += (size_t)n;
|
||||||
fputs(rbuf, stdout);
|
if (len >= cap - 1) break; /* 响应超缓冲上限,截断兜底 */
|
||||||
}
|
}
|
||||||
close(fd);
|
close(fd);
|
||||||
|
rbuf[len] = '\0';
|
||||||
|
if (!isatty(STDOUT_FILENO)) strip_ansi(rbuf);
|
||||||
|
fputs(rbuf, stdout);
|
||||||
|
free(rbuf);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1104,7 +1285,7 @@ static void usage(const char *argv0)
|
|||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"usage: %s [options] [command]\n"
|
"usage: %s [options] [command]\n"
|
||||||
" commands: start <name|all> | stop <name|all> | restart <name|all>\n"
|
" commands: start <name|all> | stop <name|all> | restart <name|all>\n"
|
||||||
" | status | reload | pid\n"
|
" | status [name|all] | reload | pid\n"
|
||||||
" no command -> start daemon (default; -f/--foreground keeps fg for supervisor)\n"
|
" no command -> start daemon (default; -f/--foreground keeps fg for supervisor)\n"
|
||||||
" options:\n"
|
" options:\n"
|
||||||
" -f, --foreground stay in foreground (supervisor fork/exec 托管)\n"
|
" -f, --foreground stay in foreground (supervisor fork/exec 托管)\n"
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ FAILED 崩溃且不再重启(超 max_restart / restart=never)
|
|||||||
AF_UNIX 单行文本:
|
AF_UNIX 单行文本:
|
||||||
|
|
||||||
```
|
```
|
||||||
meow_svcd status # 列出(daemon 未运行则打印状态文件)
|
meow_svcd status [name|all] # 列出各服务+全局(daemon 未运行则打印状态文件)
|
||||||
meow_svcd reload # 重读卡配置
|
meow_svcd reload # 重读卡配置
|
||||||
meow_svcd start <name|all>
|
meow_svcd start <name|all>
|
||||||
meow_svcd stop <name|all>
|
meow_svcd stop <name|all>
|
||||||
@@ -156,19 +156,26 @@ meow_svcd restart <name|all>
|
|||||||
meow_svcd pid
|
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 终端示例)
|
### 6.1 `systemctl` 式 CLI 用法(SSH 终端示例)
|
||||||
|
|
||||||
守护已由固件 `etc/init.d/S89meow_svcd` 开机自启,SSH 登录设备后**直接敲**命令即可,无需先知道 socket:
|
守护已由固件 `etc/init.d/S89meow_svcd` 开机自启,SSH 登录设备后**直接敲**命令即可,无需先知道 socket:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
meow_svcd status # 列出所有服务 + 全局状态
|
meow_svcd status # 列出所有服务 + 全局状态(TTY 带色;管道/重定向自动去色)
|
||||||
# # meow_svcd status
|
# meow_svcd status
|
||||||
# card_present=1 wifi_up=0 services=3
|
# card_present=1 wifi_up=0 services=3
|
||||||
# svc=mdnsd state=running pid=1234 restart=0 exit=0 trigger=boot
|
# NAME STATE PID RESTART BACKOFF TRIGGER SRC EXIT EXIT_TIME EXEC
|
||||||
# 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
|
# 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 restart mdnsd # 重启单个服务(如板载 mDNS 应答器)
|
||||||
meow_svcd start meow_netd # 启动单个服务
|
meow_svcd start meow_netd # 启动单个服务
|
||||||
@@ -181,8 +188,8 @@ meow_svcd pid # 打印守护进程 pid
|
|||||||
守护**未运行**时命令回退打印状态文件 `/tmp/meow_svcd.status`(或 `card_present=0 services=0`)并以退出码 1 返回。
|
守护**未运行**时命令回退打印状态文件 `/tmp/meow_svcd.status`(或 `card_present=0 services=0`)并以退出码 1 返回。
|
||||||
- **守护前台**:`-f`/`--foreground` —— 守护自身跑前台,供 supervisor `fork/exec`+`waitpid` 托管;
|
- **守护前台**:`-f`/`--foreground` —— 守护自身跑前台,供 supervisor `fork/exec`+`waitpid` 托管;
|
||||||
默认(无子命令)则为后台常驻。
|
默认(无子命令)则为后台常驻。
|
||||||
- 应答:成功 `OK`(`pid`→pid 数字);服务不存在 `ERR no-such-service`;`svc=... state=...` 行
|
- 应答:成功 `OK`(`pid`→pid 数字);服务不存在 `ERR no-such-service`;`status` 返回对齐表格,
|
||||||
含 `pid/restart(重启计数)/exit(上次退出码)/trigger`。
|
行内含 `state/pid/restart(重启计数)/exit(上次退出码)/trigger/来源/backoff_ms/exec`。
|
||||||
|
|
||||||
## 7. 编译
|
## 7. 编译
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
#define MUI_HINT_H 5
|
||||||
static int g_hint_last = 0; /* 上次画过的条长(清条时只清到它,避免残留) */
|
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)
|
void meow_ui_swipe_hint(int x)
|
||||||
{
|
{
|
||||||
if (!g_init_ok) return;
|
if (!g_init_ok) return;
|
||||||
@@ -361,20 +404,19 @@ void meow_ui_swipe_hint(int x)
|
|||||||
* 有状态栏(非全屏)→ y = 状态栏高度;收合(collapsed)→ y=提示线下缘
|
* 有状态栏(非全屏)→ y = 状态栏高度;收合(collapsed)→ y=提示线下缘
|
||||||
* (MUI_SBAR_COLLAPSE_H,与提示线不重叠);无状态栏(全屏/隐藏)→ y=0。 */
|
* (MUI_SBAR_COLLAPSE_H,与提示线不重叠);无状态栏(全屏/隐藏)→ y=0。 */
|
||||||
int hy = meow_ui_statusbar_height();
|
int hy = meow_ui_statusbar_height();
|
||||||
/* 单一实现(与非全屏 Hello 完全一致,全屏仅 hy 不同):grow 画 0..x、shrunk 先清旧长
|
/* 单一实现(与非全屏 Hello 完全一致,全屏仅 hy 不同):grow 画 0..x、shrunk 先把
|
||||||
* 再画 0..x、x<=0 清空。x<0 视为清空;条长上限=屏宽。 */
|
* 超出部分还原为快照、x<=0 整条还原。x<0 视为清空;条长上限=屏宽。条色仍取 th->fg。 */
|
||||||
if (x > 0) {
|
if (x > 0) {
|
||||||
if (x > g_xres) x = g_xres;
|
if (x > g_xres) x = g_xres;
|
||||||
/* 往回滑(新长度 < 上次)时,先清掉「上次画到旧长度、但本次超出的部分」,
|
if (g_hint_last == 0) hint_snapshot(); /* 首现条:拍下未污染区,供此后回填 */
|
||||||
* 否则条只增不减(右滑变长、左滑回缩实时可见)。 */
|
/* 往回滑(新长度 < 上次)时,把「上次画到旧长度、本次超出的 [x,old)」还原为快照
|
||||||
if (g_hint_last > x) meow_ui_fill_rect(0, hy, g_hint_last, MUI_HINT_H, th->bg);
|
* 真实底色(全屏 accent),而非填 th->bg —— 否则左滑回缩在 accent 底上漏黑。 */
|
||||||
/* 条色取主题前景(fg):fg 定义为与背景对比,能同时覆盖「非全屏(底=主题bg)」与
|
if (g_hint_last > x) hint_restore(x, g_hint_last);
|
||||||
* 「全屏 Hello 类(底=主题accent)」两种情况——accent 在 Hello 全屏 accent 底上同色不可见。 */
|
|
||||||
meow_ui_fill_rect(0, hy, x, MUI_HINT_H, th->fg);
|
meow_ui_fill_rect(0, hy, x, MUI_HINT_H, th->fg);
|
||||||
g_hint_last = x;
|
g_hint_last = x;
|
||||||
} else if (g_hint_last > 0) {
|
} else if (g_hint_last > 0) {
|
||||||
/* 手指回原点/抬手未达标/抬手达标:清掉此前画的条(填回背景色) */
|
/* 手指回原点/抬手未达标/抬手达标:整条还原为快照真实底色(全屏 accent 上无黑残) */
|
||||||
meow_ui_fill_rect(0, hy, g_hint_last, MUI_HINT_H, th->bg);
|
hint_restore(0, g_hint_last);
|
||||||
g_hint_last = 0;
|
g_hint_last = 0;
|
||||||
}
|
}
|
||||||
/* 不发 flush:由调用方 meow_ui_flush_region(0,0,宽,6) 只刷顶部条 */
|
/* 不发 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_fb_fd >= 0) close(g_fb_fd);
|
||||||
if (g_back_buf) free(g_back_buf);
|
if (g_back_buf) free(g_back_buf);
|
||||||
if (g_dump_path) { free(g_dump_path); g_dump_path = NULL; }
|
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_back_buf = NULL; g_fb = NULL; g_fb_mem = NULL;
|
||||||
g_fb_fd = -1; g_init_ok = 0; g_mfont_ready = 0;
|
g_fb_fd = -1; g_init_ok = 0; g_mfont_ready = 0;
|
||||||
dirty_reset();
|
dirty_reset();
|
||||||
|
|||||||
@@ -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_dx = 0, t_dy = 0; /* 按下原点(BTN_TOUCH 后首个有效 ABS 锁定) */
|
||||||
static int t_cx = 0, t_cy = 0; /* 当前坐标(-1=本次尚未见到有效坐标) */
|
static int t_cx = 0, t_cy = 0; /* 当前坐标(-1=本次尚未见到有效坐标) */
|
||||||
static int t_origin_valid = 0; /* 原点是否已锁定 */
|
static int t_origin_valid = 0; /* 原点是否已锁定 */
|
||||||
|
static uint64_t t_down_ms = 0; /* 按下时刻(monotonic ms),供实时按住时长用 */
|
||||||
|
|
||||||
/* 按下边沿:只标记按下,绝不用传入坐标立即锁原点(避免用上一手势残留坐标当原点)。
|
/* 按下边沿:只标记按下,绝不用传入坐标立即锁原点(避免用上一手势残留坐标当原点)。
|
||||||
* t_cx/t_cy 归 -1 —— 本次手势以「无坐标」起步,只接受随后到来的有效 ABS。 */
|
* t_cx/t_cy 归 -1 —— 本次手势以「无坐标」起步,只接受随后到来的有效 ABS。 */
|
||||||
@@ -136,6 +137,7 @@ static void touch_press(int x, int y)
|
|||||||
t_origin_valid = 0;
|
t_origin_valid = 0;
|
||||||
t_cx = -1; t_cy = -1;
|
t_cx = -1; t_cy = -1;
|
||||||
t_dx = 0; t_dy = 0;
|
t_dx = 0; t_dy = 0;
|
||||||
|
t_down_ms = now_ms(); /* 记按下时刻:供实时按住时长/按住视觉 */
|
||||||
}
|
}
|
||||||
/* 由 ABS_X/ABS_Y 驱动:若本次按下且原点未锁且坐标有效 → 锁原点;再更新当前坐标。 */
|
/* 由 ABS_X/ABS_Y 驱动:若本次按下且原点未锁且坐标有效 → 锁原点;再更新当前坐标。 */
|
||||||
static void touch_update(int x, int y)
|
static void touch_update(int x, int y)
|
||||||
@@ -151,7 +153,7 @@ static void touch_release(void)
|
|||||||
if (!t_down) return;
|
if (!t_down) return;
|
||||||
t_down = 0; t_press_valid = 0;
|
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;
|
t_origin_valid = 0;
|
||||||
int mdx = t_cx - t_dx, mdy = t_cy - t_dy;
|
int mdx = t_cx - t_dx, mdy = t_cy - t_dy;
|
||||||
int adx = mdx < 0 ? -mdx : mdx, ady = mdy < 0 ? -mdy : mdy;
|
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);
|
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 设备 + 解析 ---- */
|
/* ---- 输入线程:poll 所有 event 设备 + 解析 ---- */
|
||||||
|
|||||||
@@ -52,6 +52,18 @@ void meow_input_inject(int type, int a, int b);
|
|||||||
/* 清空队列(IME 打开前丢弃残留触摸,避免误敲)。 */
|
/* 清空队列(IME 打开前丢弃残留触摸,避免误敲)。 */
|
||||||
void meow_input_drain(void);
|
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)---- */
|
/* ---- IME(meow_ui_ime.c)---- */
|
||||||
/* 当前是否显示软键盘。 */
|
/* 当前是否显示软键盘。 */
|
||||||
int meow_ime_active(void);
|
int meow_ime_active(void);
|
||||||
|
|||||||
@@ -11,6 +11,20 @@
|
|||||||
|
|
||||||
enum { W_BTN = 0, W_SLIDER = 1, W_SWITCH = 2, W_LIST = 3 };
|
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 {
|
struct meow_widget {
|
||||||
int type;
|
int type;
|
||||||
int x, y, w, h;
|
int x, y, w, h;
|
||||||
@@ -89,8 +103,31 @@ void meow_widget_draw(meow_widget *w)
|
|||||||
const meow_theme *th = meow_theme_get();
|
const meow_theme *th = meow_theme_get();
|
||||||
switch (w->type) {
|
switch (w->type) {
|
||||||
case W_BTN: {
|
case W_BTN: {
|
||||||
uint32_t bg = w->pressed ? th->dim : th->accent;
|
/* 按下/按住 = 视觉态(不改 on_tap/btn_pressed/hit 语义)。按下来源两路:
|
||||||
meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, bg);
|
* - 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,
|
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);
|
w->label, th->font_body, 0xFFFFFF);
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in new issue
Block a user