- launcher meow_ui_set_statusbar(1) 启用SDK全局状态栏; 旧自绘(draw_statusbar/sb_redraw等)#if0退役(保留回退)
- 新增 SDK meow_ui_sys_feed(): launcher 喂 batt/charging/wifi/bt/vol/brightness 进SDK快照+置force+标脏顶条(0,0,xres,28)→present只重画顶条
- meow_ui_theme_reload(): SDK状态栏跟手launcher换肤; meow_sys_state 增 brightness
- 主界面/设置页/电源菜单/通知中心 顶部28px 由SDK全局状态栏统一绘制; 与App同实现/同主题/同日·夜
- ⚠️ 状态栏由MD3图标式→SDK文本式(HH:MM:SS …81% WIFI 蓝牙 VOL 亮度) — 用户拍板"用SDK的"既定结果; 图标MD3化属后续
- meow_ui.a=110c820a(仅新增符号,向后兼容) launcher=d4f108dd hello=12f48840(重编确认App不破坏); 0warning
- devdocs/plan/LAUNCHER_SDK_REWRITE.md 方案先行
282 lines
11 KiB
C
282 lines
11 KiB
C
/*
|
||
* 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 <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <unistd.h>
|
||
#include <fcntl.h>
|
||
#include <time.h>
|
||
|
||
#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);
|
||
}
|
||
|
||
/* 亮度:读 settings.conf 的 display.brightness(launcher 同源),并 clamp 到
|
||
* [display.brightness_min, 100](与 launcher clamp_brightness 同口径,下限默认 1=极暗非全黑)。
|
||
* Stage-One 状态栏收敛(launcher 退役自绘状态栏)后,launcher 侧用 meow_ui_sys_feed() 喂
|
||
* 即时值(写入方=launcher);此处兜底让 App(meow_ui_run 的 1s 轻刷)也读到并显示亮度,
|
||
* 保证「launcher / App 同一实现、同日/夜」。 */
|
||
static int settings_bright(void)
|
||
{
|
||
int v = meow_ui_vol_read(MEOW_SETTINGS_FILE, "display.brightness", 101);
|
||
int mn = meow_ui_vol_read(MEOW_SETTINGS_FILE, "display.brightness_min", 1);
|
||
if (v < mn) v = mn;
|
||
if (v > 100) v = 100;
|
||
return v;
|
||
}
|
||
|
||
int meow_ui_sys_init(void)
|
||
{
|
||
memset(&g_sys, 0, sizeof g_sys);
|
||
g_sys.batt = -1;
|
||
g_sys.vol = settings_vol();
|
||
g_sys.brightness = settings_bright();
|
||
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();
|
||
g_sys.brightness = settings_bright(); /* 兜底读 settings;launcher 喂入会覆盖(更即时) */
|
||
|
||
/* 蓝牙: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; }
|
||
|
||
/* ---- 系统进程(launcher)喂入权威快照(Stage-One 状态栏收敛)----
|
||
* launcher 不跑 SDK 事件循环,其 g_sys(meow_sys_refresh/light + 事件路径维护)是权威数据源
|
||
* (尤其亮度/音量,写入方=launcher)。此处把它覆盖进 SDK 快照并置 force,驱动下一次
|
||
* present() 重画顶条。不喂的字段保持 SDK 现有值(由 meow_ui_sys_refresh 同源兜底)。 */
|
||
void meow_ui_sys_feed(const meow_ui_feed *f)
|
||
{
|
||
if (!f) return;
|
||
g_sys.batt = f->batt;
|
||
g_sys.charging = f->charging;
|
||
g_sys.wifi_on = f->wifi_on;
|
||
g_sys.bt_seen = f->bt_seen;
|
||
g_sys.bt_on = f->bt_on;
|
||
g_sys.vol = f->vol;
|
||
if (f->brightness <= 0) g_sys.brightness = 0; /* 未知/屏关:不入顶条(>0 才显示) */
|
||
else if (f->brightness > 100) g_sys.brightness = 100;
|
||
else g_sys.brightness = f->brightness;
|
||
g_statusbar_force = 1; /* 数据已变 → 顶条必重画 */
|
||
}
|
||
|
||
/* ---- 公共 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% 亮度nn%(同 launcher 状态栏右侧语义,文本式)。
|
||
* mfont 就绪用 CJK「蓝牙/亮度」,否则回退 ASCII "BT/BRI"(28px 栏紧凑,避免超宽叠到时钟)。
|
||
* 顺序与 launcher 右侧簇一致:电量 → WiFi → 蓝牙 → 音量 → 亮度。*/
|
||
static void statusbar_right_str(char *out, size_t n)
|
||
{
|
||
char batt[16] = "", wifi[16] = "", vol[20] = "", bt[16] = "", bri[20] = "";
|
||
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");
|
||
if (g_sys.brightness > 0) /* >0 才显示(屏常亮态);0=屏关/未知不入 */
|
||
snprintf(bri, sizeof bri, meow_gfx_mfont_ready() ? "亮度%d%%" : "BRI %d%%", g_sys.brightness);
|
||
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); }
|
||
if (bri[0]) { if (out[0]) strcat(out, " "); strcat(out, bri); }
|
||
}
|
||
|
||
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;
|
||
}
|