Files
AskaEth 2826c1e905 feat: M6.40批 — 阅读器UI改版(书架list·grid+空态骨架+界面设置+App设置页+header·footer+背景黑默认) + 系统级音量翻页映射 + P1 MD3(select面板28/对话框按钮胶囊) + MeowPanel写端点护栏 + SDK公开仓·文档同步
- reader: app.c/shelf.c/reader.c 全套UI; 新增 reader_conf(reader.conf)/settings(App设置页)/reader_ipc(音量翻页IPC响应); app.json加volume_page
- launcher: keys.c meow_volpage_*门 + util.c app_id→fd映射(HELLO记fd/断开清) + app_launcher.c 前台三处接线(进入sink/按键路径volpage_key/离开clear) → 前台声明volume_page的App音量键→MSG_KEY_PAGEUP/PAGEDOWN翻页(非阅读器前台音量照旧)
- sdk: meow_select.c 底部面板28 + meow_ui_dialog.c 对话框按钮胶囊(h/2) + test_meow_ui_select.c修采样
- meowpanel: /api/net/policy·/api/power/policy 加防自锁+policy默认关两条护栏(panel.policy_enabled默认0)
- 文档: MEOW_APP_SDK.md §12.6 + meowpanel README端点表

0warning;宿主测试全过;二进制留批量批次未入库
2026-09-04 18:13:18 +08:00

304 lines
12 KiB
C
Raw Permalink 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.
/* keys.c — MeowOS launcher 拆分模块(LAUNCHER_SPLIT_PLAN 拆分)。*/
/* 本文件由拆分工具从 meow_launcherd.c 按职责搬移;逻辑与原文 0-diff(仅 static 可见性按跨模块需求调整)。*/
#include "meow_launcher_internal.h"
int kd_tick(struct kd_state *s, int raw)
{
unsigned long long now = now_ms();
int ev = KD_NONE;
if (raw != s->raw) {
if (s->raw == 0 && raw != 0) { /* 任何跳变都重记稳定窗口(含原始 raw 变化) */ }
s->raw = raw; s->since = now;
}
int want = s->raw;
if (want != s->stable) {
if (now - s->since >= KEY_DEBOUNCE_MS) {
s->stable = want;
if (s->stable && !s->down) { s->down = 1; s->down_at = now; s->longpressed = 0; ev = KD_DOWN; }
else if (!s->stable && s->down) { s->down = 0; ev = KD_UP; }
}
}
if (s->down && !s->longpressed && now - s->down_at >= CLICK_MS) s->longpressed = 1;
return ev;
}
/* Unblank the panel AFTER the player is frozen: with the player SIGSTOPped
* it can no longer re-blank, so the menu (drawn on fb0) stays visible even
* when the menu opened while the player's screen timeout had blanked the
* panel. The blank state still belongs to the player's state machine — on
* session end restore_backlight() re-blanks when menu_opened_blank, so the
* player resumes into a consistent (blanked) state. */
void unblank_panel(void)
{
int fbfd = open(FB_DEV, O_RDWR);
if (fbfd >= 0) { ioctl(fbfd, FBIOBLANK, 0); close(fbfd); }
logmsg("panel unblanked (player frozen)");
}
void meow_panel_off(void)
{
meow_saved_brightness = read_brightness();
if (meow_saved_brightness <= 0) meow_saved_brightness = 60;
write_brightness(0, "meow_panel_off");
if (screen_off_mode == 0) {
/* panel:面板下电 + FBIOPBLANK(4)(同时挂起 goodix 触摸,无误触) */
int f = open(FB_DEV, O_RDWR);
if (f >= 0) { ioctl(f, FBIOBLANK, 4); close(f); }
} else {
/* black:仅背光 0 全黑(面板仍上电,触摸可唤醒) */
}
meow_screen_on = 0;
logmsg("meow panel OFF (mode=%s brightness 0%s)",
screen_off_mode ? "black" : "panel",
screen_off_mode ? "" : " + blank 4");
}
void meow_panel_on(void)
{
int f = open(FB_DEV, O_RDWR);
if (f >= 0) { ioctl(f, FBIOBLANK, 0); close(f); }
/* 亮度恢复以**用户设置**为准,而非 meow_saved_brightness(可能过期)。
* watchdog 独立息屏(wd_panel_off 直接 write_brightness(0),见 meow_watchdog.c)**不会**
* 更新本进程 meow_saved_brightness——若用其恢复会回错值(真机「PIN 亮屏亮度跳高」,
* 2026-09-03)。settings 已从盘上读到→按 display.brightness(clamp)恢复;未读到(早启动)
* →回退 meow_saved_brightness 现值(保持旧行为,防开机竞态)。 */
if (meow_settings_loaded()) {
int b = meow_settings_brightness_clamped();
if (b > 0) { write_brightness(b, "meow_panel_on"); meow_saved_brightness = b; }
} else {
write_brightness(meow_saved_brightness > 0 ? meow_saved_brightness : 60, "meow_panel_on");
}
meow_screen_on = 1;
/* M2.8 Bug 3-residual:亮屏必须用 fb_pan_buf0_force 无条件重发 pan。fb_norm_buf0 在
* 缓存 fb_active_yoffset==0 时提前返回,而 FBIOBLANK(4)->(0) 后 LCDC 扫描地址可能已复位
* 到非 buffer0 / 失效位置,缓存仍是 0 导致真正的 re-pan 从未发出 —— M2.7 只加 fb_norm_buf0
* 仍黑屏的原因。主界面都画在 buffer0(mmap 偏移 0),此处强制 re-pan 保证唤醒后落在 buffer0。
* 当前界面内容的重绘由调用方(run_menu 醒屏分支)经 meow_repaint_surface 完成。 */
fb_pan_buf0_force();
logmsg("meow panel ON (blank 0 + brightness %d)", meow_saved_brightness);
}
void meow_toggle_panel(void) { if (meow_screen_on) meow_panel_off(); else meow_panel_on(); }
int kp_open(struct keypoll *kp, const char *dev)
{
memset(kp, 0, sizeof *kp);
kp->fd = open(dev, O_RDONLY | O_NONBLOCK);
if (kp->fd < 0) { logmsg("open %s: %s", dev, strerror(errno)); return -1; }
if (ioctl(kp->fd, EVIOCGKEY(sizeof kp->cur), kp->cur) < 0) {
logmsg("EVIOCGKEY %s: %s", dev, strerror(errno));
close(kp->fd); return -1;
}
memcpy(kp->prev, kp->cur, sizeof kp->cur);
kp->ok = 1;
logmsg("keypoll %s ready", dev);
return 0;
}
/* 关闭 keypoll(设置页/下拉框等自开设备用;幂等,ok=0 时不动) */
void kp_close(struct keypoll *kp)
{
if (kp->ok && kp->fd >= 0) close(kp->fd);
kp->ok = 0;
}
/* Poll device key state; call cb(code, is_down) for each edge.
* Returns 1 if any edge. */
int kp_poll(struct keypoll *kp, int (*cb)(int code, int down, void *ctx),
void *ctx)
{
if (!kp->ok) return 0;
if (ioctl(kp->fd, EVIOCGKEY(sizeof kp->cur), kp->cur) < 0) return 0;
int changed = 0;
for (int w = 0; w < KEYBUF_WORDS; w++) {
if (kp->cur[w] == kp->prev[w]) continue;
for (int b = 0; b < 32; b++) {
int c = (kp->cur[w] >> b) & 1;
int p = (kp->prev[w] >> b) & 1;
if (c != p) {
int code = w * 32 + b;
changed = 1;
if (cb) cb(code, c, ctx);
}
}
kp->prev[w] = kp->cur[w];
}
return changed;
}
int kp_is_down(const struct keypoll *k, int code)
{
return (k->cur[code / 32] >> (code % 32)) & 1;
}
int hotkey_mode = HOTKEY_MODE_PWRVOL;
int seq_codes[MAX_SEQ_LEN];
int seq_len = 0;
int seq_gap_ms = 600;
static int key_name_code(const char *n)
{
if (!strcmp(n, "volup")) return KEY_VOLUMEUP;
if (!strcmp(n, "voldown")) return KEY_VOLUMEDOWN;
if (!strcmp(n, "play")) return KEY_PLAYPAUSE;
if (!strcmp(n, "next")) return KEY_NEXT;
if (!strcmp(n, "previous")) return KEY_PREVIOUSSONG;
if (!strcmp(n, "power")) return KEY_POWER;
return -1;
}
void load_config(void)
{
menu_bg = 0x000000;
hotkey_mode = HOTKEY_MODE_PWRVOL; /* default: hold power + VOL_DOWN tap */
seq_len = 0;
seq_gap_ms = 600;
char path[256];
snprintf(path, sizeof path, "%s/meow.conf", MEOWAPPS);
FILE *f = fopen(path, "r");
if (f) {
char buf[128];
while (fgets(buf, sizeof buf, f)) {
if (!strncmp(buf, "bg_color=", 9)) {
unsigned int v;
if (sscanf(buf + 9, "%x", &v) == 1) menu_bg = v & 0xFFFFFF;
} else if (!strncmp(buf, "hotkey=", 7)) {
char *p = buf + 7;
while (*p == ' ' || *p == '\t') p++;
if (!strncmp(p, "seq", 3)) hotkey_mode = HOTKEY_MODE_SEQ;
else hotkey_mode = HOTKEY_MODE_PWRVOL;
} else if (!strncmp(buf, "hotkey_seq=", 11)) {
char *p = buf + 11;
seq_len = 0;
/* BUGFIX: the outer loop must consume the EOL — with
* *p staying at '\n' the empty-name branch never advanced p
* and the daemon spun forever (found on-device: a hotkey_seq
* line wedged load_config in a busy loop). */
while (*p && *p != '\n' && *p != '\r' && seq_len < MAX_SEQ_LEN) {
char name[16];
int k = 0;
while (*p && *p != ',' && *p != '\n' && *p != '\r' && k < 15)
name[k++] = *p++;
name[k] = 0;
if (k >= 15) /* malformed long name: skip to separator/EOL */
while (*p && *p != ',' && *p != '\n' && *p != '\r') p++;
if (*p == ',') p++;
int c = key_name_code(name);
if (c > 0) seq_codes[seq_len++] = c;
}
} else if (!strncmp(buf, "hotkey_seq_gap=", 15)) {
int g = atoi(buf + 15);
if (g >= 200 && g <= 2000) seq_gap_ms = g;
}
}
fclose(f);
}
if (seq_len == 0) { seq_codes[0] = KEY_VOLUMEUP; seq_len = 3; } /* seq fallback volup x3 */
if (hotkey_mode == HOTKEY_MODE_PWRVOL)
logmsg("config: bg_color=%06x hotkey=power_voldown (window %d-%dms)",
menu_bg, HK_PWRWIN_MIN, HK_PWRWIN_MAX);
else
logmsg("config: bg_color=%06x hotkey=seq (%d taps of code %d gap=%dms)",
menu_bg, seq_len, seq_codes[0], seq_gap_ms);
}
/* =========================================================================
* 系统级音量键翻页门(READER_UI_DESIGN §E;用户 2026-09-04 拍板)。
*
* 语义:音量键是系统资源(默认调音量)。本门在「当前台 App 为其 app.json 声明
* volume_page 的应用(MVP:os.meow.reader)」时,把 VOL_UP/VOL_DOWN 转换成系统翻页
* 信号(MSG_KEY_PAGEUP/PAGEDOWN,meow_ipc.h)经 IPC 下发给该前台 App;其余情况一概
* 放行(return 0),音量基本行为/插孔消抖/音量反转等绝不破坏。
*
* 职责分离(关键,且最小侵入):
* - 本模块只提供「判定 + 门」:meow_volpage_app_allows() 读 app.json 声明;
* meow_volpage_key() 在「已武装 sink 且按下 VOL」时消费并发下发;
* - 武装(sink 注册)= 前台应用进入体积页声明 app 时调用 meow_volpage_sink()
* (应用存在连接后其 fd 生效);离开/退出时 meow_volpage_sink_clear()。
* 该接线落在「前台会话的按键路径」(app_launcher.c / meow_launcherd.c 主循环),
* 属主会话统一收编范围,此处不越界改动。
*
* 声明字段(app.json):`"volume_page": true`(或 1)。未来其它 App 可声明复用。
* ========================================================================= */
/* 前台翻页 sink(本模块私有;单写者=前台会话上下文,读=按键门)。 */
static char g_volpage_app[MEOW_APP_ID_MAX] = "";
static int g_volpage_fd = -1;
void meow_volpage_sink_clear(void); /* 前向声明(sink 内调用) */
/* 读 <MEOWAPPS>/<app_id>/app.json 是否声明 volume_page=true|1。1=声明;0=否/读不到。 */
int meow_volpage_app_allows(const char *app_id)
{
if (!app_id || !app_id[0]) return 0;
char path[320];
snprintf(path, sizeof path, "%s/%s/app.json", MEOWAPPS, app_id);
FILE *f = fopen(path, "r");
if (!f) return 0;
/* 轻量扫描:找 "volume_page" → 跳过空白 → true/1。app.json 为紧凑 JSON。 */
char buf[1024];
buf[0] = 0;
size_t rd = fread(buf, 1, sizeof buf - 1, f);
buf[rd] = 0;
fclose(f);
const char *p = buf;
while ((p = strstr(p, "volume_page")) != NULL) {
p += strlen("volume_page");
while (*p == ' ' || *p == '\t' || *p == ':' || *p == '"') p++;
if (strncmp(p, "true", 4) == 0) return 1;
if (*p == '1' && (p[1] < '0' || p[1] > '9')) return 1;
/* 未命中则继续找(防字段重复);跳过本段 */
while (*p && *p != ',' && *p != '}') p++;
}
return 0;
}
/* 武装/清除 sink。app_id 非空且 fd>=0 → 记录;否则清除。幂等(同 app_id+fd 不重复记日志,
* 供前台会话循环每圈安全重调,也兼容断线重连换 fd)。 */
void meow_volpage_sink(const char *app_id, int fd)
{
if (!app_id || !app_id[0] || fd < 0) { meow_volpage_sink_clear(); return; }
if (g_volpage_fd == fd && !strcmp(g_volpage_app, app_id)) return; /* 同 app_id+fd:幂等 */
snprintf(g_volpage_app, sizeof g_volpage_app, "%s", app_id);
g_volpage_fd = fd;
logmsg("volpage: sink armed app=%s fd=%d", g_volpage_app, g_volpage_fd);
}
void meow_volpage_sink_clear(void)
{
if (g_volpage_fd >= 0)
logmsg("volpage: sink cleared app=%s", g_volpage_app[0] ? g_volpage_app : "?");
g_volpage_app[0] = 0;
g_volpage_fd = -1;
}
int meow_volpage_armed(void) { return g_volpage_fd >= 0; }
/* 按键门:按下 VOL_UP/DOWN 且 sink 已武装 → 下发翻页信号并消费(返回 1)。
* 未武装 / 非 VOL / 抬起 → 返回 0(放行给音量路径)。 */
int meow_volpage_key(int code, int is_down)
{
if (!is_down) return 0; /* 只响应按下边沿 */
if (g_volpage_fd < 0) return 0; /* 未武装:音量照旧 */
if (code == KEY_VOLUMEUP) {
(void)meow_ipc_send_pub(g_volpage_fd, MSG_KEY_PAGEUP, 1, NULL, 0);
return 1;
}
if (code == KEY_VOLUMEDOWN) {
(void)meow_ipc_send_pub(g_volpage_fd, MSG_KEY_PAGEDOWN, 1, NULL, 0);
return 1;
}
return 0;
}