/* 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); 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); } write_brightness(meow_saved_brightness > 0 ? meow_saved_brightness : 60); 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); }