Files
MeowOS-SDK/meow_ui_sys.c
T
MeowOS 9f4143af22 启动器SDK改造 阶段一: 状态栏收敛单一SDK全局(agent-136)
- 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 方案先行
2026-08-30 18:42:59 +08:00

282 lines
11 KiB
C
Raw 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.
/*
* 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;
}