/* * meow_ui_sys.c — SDK 内置系统状态读取器 + 全局状态栏(A-0 核心)。 * * 职责: * 1. 状态读取器:读 /sys/class/power_supply/(电量/充电)、WiFi operstate、 * 蓝牙实上电、音量(/usr/data/meow/settings.conf 的 audio.volume,与 launcher * 同源单点);时钟在绘制时现取 localtime。1s 轻刷,蓝牙 30s 缓存。 * 2. 全局状态栏绘制:把时钟/电量/WiFi/蓝牙/音量画进 back_buf 顶部 MUI_SBAR_H 行; * 由渲染后端 present() 前置调用(z 最顶),非全屏可见。 * 3. 显隐/全屏状态 + 公共 API(meow_ui_set_statusbar / set_fullscreen / ...)。 * * 注意:所有读取在事件循环主线内做(无独立线程),1s 频率同步读取 sysfs/小文件, * 开销可忽略;蓝牙 hciconfig 为 popen(spawn) 较贵,仅 30s 刷一次并缓存。 */ #include "meow_ui_sys.h" #include "meow_ui_internal.h" #include "meow_ui_vol.h" #include #include #include #include #include #include #define MEOW_SETTINGS_FILE "/usr/data/meow/settings.conf" /* 蓝牙 powered 缓存刷新间隔(hciconfig 较贵) */ #define BT_REFRESH_MS 30000 static meow_sys_state g_sys; static int g_sys_inited = 0; /* 显隐/全屏状态(公共 API 与绘制/输入共用) */ static int g_statusbar_on = 1; /* 默认显示 */ static int g_fullscreen = 0; /* 默认非全屏 */ static int g_statusbar_force = 0; /* 1s 刷新后置位,强制重画顶部条 */ static uint64_t now_ms(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (uint64_t)ts.tv_sec * 1000u + (uint64_t)ts.tv_nsec / 1000000u; } /* 读一份 sysfs/文件字符串(剥尾随换行);失败回退 "" */ static void read_str(const char *path, char *buf, size_t n) { buf[0] = 0; int fd = open(path, O_RDONLY); if (fd < 0) return; ssize_t r = read(fd, buf, n - 1); close(fd); if (r <= 0) { buf[0] = 0; return; } buf[r] = 0; while (r > 0 && (buf[r-1] == '\n' || buf[r-1] == '\r')) buf[--r] = 0; } static int read_int(const char *path) { char buf[24]; read_str(path, buf, sizeof buf); if (!buf[0]) return -1; return atoi(buf); } static int hci_powered(void) { FILE *p = popen("hciconfig -a hci0 2>/dev/null", "r"); if (!p) return 0; int up = 0; char buf[256]; while (fgets(buf, sizeof buf, p)) { char *s = buf, *tok; while ((tok = strsep(&s, " \t\r\n")) != NULL) { if (!strcmp(tok, "UP")) { up = 1; break; } } } pclose(p); return up; } /* 音量:读 settings.conf 的 audio.volume(launcher 同源,watchdog 写同文件)。 * 与 launcher 口径一致:0-100,且尊重 audio.volume_max 上限 clamp(vol_clamp 同义)。 * 注意 settings 落盘为 "key = value"(带空格),必须按 '=' 切分解析,不能朴素前缀匹配—— * 否则永远读不到(回退默认 60,即「状态栏音量数字不对、卡在 60」的根因)。 */ static int settings_vol(void) { int vol = meow_ui_vol_read(MEOW_SETTINGS_FILE, "audio.volume", MEOW_UI_VOL_DEFAULT); int vol_max = meow_ui_vol_read(MEOW_SETTINGS_FILE, "audio.volume_max", MEOW_UI_VOL_MAX_DEF); return meow_ui_vol_clamp(vol, vol_max); } int meow_ui_sys_init(void) { memset(&g_sys, 0, sizeof g_sys); g_sys.batt = -1; g_sys.vol = settings_vol(); g_statusbar_on = 1; g_fullscreen = 0; g_statusbar_force = 1; meow_ui_sys_refresh(); g_sys_inited = 1; return 0; } void meow_ui_sys_refresh(void) { g_sys.batt = read_int("/sys/class/power_supply/battery/capacity"); if (g_sys.batt < 0 || g_sys.batt > 100) g_sys.batt = -1; char st[16]; read_str("/sys/class/power_supply/battery/status", st, sizeof st); g_sys.charging = (strstr(st, "Charg") || strstr(st, "Full")) ? 1 : 0; char op[16]; read_str("/sys/class/net/wlan0/operstate", op, sizeof op); g_sys.wifi_on = !strcmp(op, "up") ? 1 : 0; g_sys.vol = settings_vol(); /* 蓝牙:hci0 存在性检查便宜;上电状态贵(hciconfig spawn),30s 缓存。 */ static uint64_t last_bt_ms = 0; uint64_t n = now_ms(); g_sys.bt_seen = access("/sys/class/bluetooth/hci0", F_OK) == 0; if (!g_sys.bt_seen) { g_sys.bt_on = 0; last_bt_ms = n; } else if (!last_bt_ms || (n - last_bt_ms) >= BT_REFRESH_MS) { g_sys.bt_on = hci_powered(); last_bt_ms = n; } /* 时钟每秒变 → 强制顶条重画(时间在绘制时现取) */ g_statusbar_force = 1; } const meow_sys_state *meow_ui_sys_get(void) { return &g_sys; } /* ---- 公共 API ---- */ void meow_ui_set_statusbar(int visible) { g_statusbar_on = visible ? 1 : 0; g_statusbar_force = 1; /* 显隐切换也要重画顶条 */ } int meow_ui_statusbar_visible(void) { return g_statusbar_on && !g_fullscreen; } void meow_ui_set_fullscreen(int on) { g_fullscreen = on ? 1 : 0; g_statusbar_force = 1; /* 进出全屏立即反映(显示/隐藏顶条) */ } int meow_ui_statusbar_height(void) { if (!meow_ui_sys_statusbar_active()) return 0; return meow_ui_statusbar_collapsed() ? MUI_SBAR_COLLAPSE_H : MUI_SBAR_H; } /* ---- 内部(gfx/input 用) ---- */ int meow_ui_sys_statusbar_active(void) { /* collapsed 视为状态栏仍激活(只留提示线):与 fullscreen 并存(全屏启 collapsed)—— * 这样 gfx.c present() 的收合动画块会运行(驱动 g_sbar_collapse_h 插值),且顶部提示线 * 正常绘制/热区可用。fullscreen 且不 collapsed → 整条隐藏(沉浸,inset=0),保持既有语义。 */ return g_statusbar_on && (meow_ui_statusbar_collapsed() || !g_fullscreen); } int meow_ui_sys_statusbar_height(void) { if (!meow_ui_sys_statusbar_active()) return 0; return meow_ui_statusbar_collapsed() ? MUI_SBAR_COLLAPSE_H : MUI_SBAR_H; } void meow_ui_sys_notif_pull(void) { /* 系统级「下拉展开通知中心」手势接线(M2.5x):输入层识别出「起始于顶部热区的 * SWIPE_DOWN」后路由到此。通知中心 UI 为后续里程碑——此处先落记录(避免哑无痕); * 届时由对应代理在该入口挂真实展开逻辑(IPC SHOW_ACTIVITY / 覆盖层渲染)。 */ meow_log(MEOW_LOG_WARN, "notif_pull: notification center TBD (wired)"); } int meow_ui_sys_statusbar_force(void) { int f = g_statusbar_force; g_statusbar_force = 0; return f; } /* 状态栏右侧信息串:batt% WIFI 蓝牙/蓝牙 VOL nn%(同 launcher statusbar_right_str) */ static void statusbar_right_str(char *out, size_t n) { char batt[16] = "", wifi[16] = "", vol[20] = "", bt[16] = ""; if (g_sys.batt >= 0) snprintf(batt, sizeof batt, "%d%%", g_sys.batt); if (g_sys.wifi_on) snprintf(wifi, sizeof wifi, "WIFI"); snprintf(vol, sizeof vol, "VOL %d%%", g_sys.vol); if (g_sys.bt_seen && g_sys.bt_on) snprintf(bt, sizeof bt, meow_gfx_mfont_ready() ? "蓝牙" : "BT"); out[0] = 0; if (batt[0]) strcat(out, batt); if (wifi[0]) { if (out[0]) strcat(out, " "); strcat(out, wifi); } if (bt[0]) { if (out[0]) strcat(out, " "); strcat(out, bt); } if (vol[0]) { if (out[0]) strcat(out, " "); strcat(out, vol); } } void meow_ui_sys_statusbar_render(void) { if (!meow_ui_sys_statusbar_active()) return; const meow_theme *th = meow_theme_get(); int w = meow_ui_width(); int h = MUI_SBAR_H; /* 整条清底:覆盖 app 可能画进顶行的内容(z 最顶) */ meow_ui_fill_rect(0, 0, w, h, th->bg); /* 文字垂直锚 y:(栏高-行高)/2 居中;下限 1 防极端行高 > 栏高时负 y 溢出顶端(不越界优先)。 */ int ty = (h - meow_ui_text_height(MUI_SBAR_FONT)) / 2; if (ty < 1) ty = 1; /* 时钟(左):每秒现取 localtime */ char timebuf[16]; time_t t = time(NULL); struct tm tm; localtime_r(&t, &tm); snprintf(timebuf, sizeof timebuf, "%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec); meow_ui_text(8, ty, timebuf, MUI_SBAR_FONT, th->fg); /* 右侧信息串:batt% WIFI 蓝牙 VOL nn% */ char right[96]; statusbar_right_str(right, sizeof right); if (right[0]) meow_ui_text_right(w - 8, ty, right, MUI_SBAR_FONT, th->fg); /* 分隔线(栏内最底 1px,主题次要色);内容 inset=statusbar_height()=28 从 y=28 起即干净 */ meow_ui_fill_rect(0, h - 1, w, 1, th->dim); g_statusbar_force = 0; }