Files
MeowOS-For-R3P2/patch_v2/sdk/meow_ui_sys.c
T

384 lines
18 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 "meow_notify_ipc.h" /* meow_notify_ipc_notif_open:App 前台 #10.5 上报 */
#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"
/* App 前台顶条模式文件(App 进程 SDK 写,launcher 读)。与 /tmp/meowapp_state(immersive,launcher 写/
* watchdog 读)语义不同,独立文件避免冲突。值:'0'=展开 28px 整条;'1'=收合(提示线);'2'=隐藏(沉浸 inset0)。
* 仅 App 进程写(!g_sys_render);launcher(系统 UI)不写,只读。App 退出时由 launcher unlink。 */
#define MEOW_SBAR_MODE_FILE "/tmp/meowapp_sbar"
/* 蓝牙 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 刷新后置位,强制重画顶部条 */
/* 「同一个状态栏」闭环(§3.1/FRAMEWORK_DESIGN §10.2):状态栏 = 系统级组件,仅系统进程
* (launcher) 渲染。g_sys_render=1 表示本进程是系统 UI —— meow_ui_sys_statusbar_render /
* present() 据此决定是否画出顶条/提示线;App 进程保持 0(不渲染)+ 仍 inset 28(launcher 叠加)。 */
static int g_sys_render = 0; /* 1=本进程为系统 UI(渲染);0=App(不渲染,仅 inset) */
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_connected = !strcmp(op, "up") ? 1 : 0;
g_sys.wifi_on = g_sys.wifi_connected; /* SDK 自身读不到射频 vs 关联的区分,一起走 operstate */
g_sys.wifi_level = -1000; /* 信号分档由 launcher 喂(唯一权威源);SDK 不读 /proc 无线 */
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.wifi_connected = f->wifi_connected;
g_sys.wifi_level = f->wifi_level;
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; /* 数据已变 → 顶条必重画 */
}
/* ---- 系统 UI 模式(「同一个状态栏」闭环;launcher 调用)----
* launcher(系统进程)链 meow_ui.a 后调 meow_ui_sys_set_system_mode(1):本进程渲染全局状态栏
* (present() 前置合成 + App 前台叠加)。App 进程默认 0:不渲染状态栏、只 inset(§6.1 ③ /
* FRAMEWORK_DESIGN §10.2)。meow_ui_sys_statusbar_active()/meow_ui_statusbar_height() 不随此
* 开关变(App 仍报告 28px inset + 顶部热区),仅渲染路径受控。 */
void meow_ui_sys_set_system_mode(int on)
{
g_sys_render = on ? 1 : 0;
g_statusbar_force = 1; /* 切换即强制下次 present 反映(画/不画顶条) */
}
/* ---- App 前台顶条模式上报(agent-146 全屏收合接入)----
* 仅在 App 进程(!g_sys_render)写;launcher(system_mode=1)不写,避免覆盖 App 的状态。
* 由 meow_ui_set_fullscreen / meow_ui_statusbar_set_collapsed 调用,使 launcher(read 进程)
* 能感知 App 运行时的全屏/收合态,从而把新顶条切到收合(提示线)而非仍画 28px 整条。 */
void meow_ui_sbar_write_mode(void)
{
if (g_sys_render) return; /* 系统进程(launcher)不写 */
char m = meow_ui_statusbar_collapsed() ? '1'
: (g_fullscreen ? '2' : '0');
int fd = open(MEOW_SBAR_MODE_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) return;
if (write(fd, &m, 1) < 0) { /* ignore write error */ }
close(fd);
}
int meow_ui_sys_render_enabled(void)
{
return g_sys_render;
}
/* ---- 公共 API ---- */
void meow_ui_set_statusbar(int visible)
{
g_statusbar_on = visible ? 1 : 0;
g_statusbar_force = 1; /* 显隐切换也要重画顶条 */
meow_ui_sbar_write_mode(); /* App 进程上报顶条模式(launcher 感知全屏/收合) */
}
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; /* 进出全屏立即反映(显示/隐藏顶条) */
meow_ui_sbar_write_mode(); /* App 进程上报顶条模式(launcher 感知全屏/收合) */
}
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(int origin_y)
{
/* 系统级「下拉展开通知中心」手势接线(FRAMEWORK_DESIGN §10.5):输入层识别出「起始于
* 顶部热区的 SWIPE_DOWN」后路由到此。App 进程只做检测+上报(不发事件给 on_swipe),
* 通知中心仍由 launcher 渲染——此处经 IPC 发 MSG_NOTIF_OPEN 请求 launcher 展开叠加会话
* (SIGSTOP App + 读回其帧 + 叠加渲染通知中心)。best-effort:无 socket/launcher 不在
* → meow_notify_ipc_notif_open 静默降级。 */
static uint32_t seq = 0;
if (origin_y < 0) origin_y = 0;
meow_notify_ipc_notif_open(NULL, ++seq, 0, (uint16_t)origin_y,
(uint16_t)meow_ui_statusbar_hotzone(),
(uint8_t)(g_fullscreen ? 1 : 0));
}
int meow_ui_sys_statusbar_force(void)
{
int f = g_statusbar_force;
g_statusbar_force = 0;
return f;
}
/* ---- 右侧信息簇(MD3 图标 + 变量值,取代文本式 statusbar_right_str)----
* 「同一个状态栏」右侧 = 图标+值:电量图标+% / 音量图标+% / 蓝牙图标 / WiFi 图标(信号分档)/
* 亮度图标+%(可选,与时钟不重叠才画)。图标位图来自 sb_icons.h(复用 launcher);
* 文字=紧凑数值(%d%%),无 CJK 依赖;颜色随 meow_theme(日/夜)由主题表提供。
* 顺序(右→左):电量(最右)→ 音量 → 蓝牙 → WiFi → 亮度。 */
/* 注:旧文本式右侧串 statusbar_right_str 已退役为图标簇(同上),不再保留调用路径。 */
/* 画一个右对齐项:右边界=*xr,画完向左推进 *xr(含 MEOW_SB_XGAP 项间隙)。
* icon>=0 画图标(可配右侧数值 txt;txt==NULL 仅图标)。文字/图标同用主题色 col。
* ink_h 传给 meow_gfx_blit_icon(主图标用 MEOW_SB_ICON_INK 等大;WiFi 强弱档传 0 保持自然 ink)。
* ycy = 图标 ink 垂直中心线(收合上移时随 -off 上移);ty = 文字 y。返回该项左边缘(供亮度可选判宽)。 */
static int sb_r_place(int *xr, int icon, const char *txt, uint32_t col, int ink_h, int ty, int ycy)
{
int iconw = (icon >= 0) ? MEOW_SB_ICON_SIZE : 0;
int gap = (icon >= 0 && txt) ? MEOW_SB_TGAP : 0;
int tw = txt ? meow_ui_text_width(txt, MUI_SBAR_FONT) : 0;
int x = *xr - (iconw + gap + tw);
if (icon >= 0) { meow_gfx_blit_icon(icon, x, 0, ink_h, col, ycy); x += iconw + gap; }
if (txt) meow_ui_text_right(*xr, ty, txt, MUI_SBAR_FONT, col);
*xr -= (iconw + gap + tw) + MEOW_SB_XGAP;
return *xr + MEOW_SB_XGAP;
}
/* WiFi 信号分档(g_sys.wifi_connected + wifi_level)→ 图标索引。
* 网卡关 → -1(不画);开未关联 → SB_I_WIFI_NO;已关联 → 按 dBm 分 STRONG/MEDIUM/WEAK(未知→满格)。 */
static int sb_wifi_icon(void)
{
if (!g_sys.wifi_on) return MEOW_SB_ICON_WIFI_OFF;
if (!g_sys.wifi_connected) return MEOW_SB_ICON_WIFI_NO;
if (g_sys.wifi_level <= -1000) return MEOW_SB_ICON_WIFI_STRONG; /* 未知 → 满格 */
if (g_sys.wifi_level >= -60) return MEOW_SB_ICON_WIFI_STRONG;
if (g_sys.wifi_level >= -75) return MEOW_SB_ICON_WIFI_MEDIUM;
return MEOW_SB_ICON_WIFI_WEAK;
}
void meow_ui_sys_statusbar_render(void)
{
meow_ui_sys_statusbar_render_off(0);
}
void meow_ui_sys_statusbar_render_off(int yoff)
{
if (!g_sys_render) return; /* 仅系统进程(launcher)渲染;App 不画顶条 */
if (!meow_ui_sys_statusbar_active()) return;
if (yoff < 0) yoff = 0;
if (yoff > MUI_SBAR_H) yoff = MUI_SBAR_H;
const meow_theme *th = meow_theme_get();
int w = meow_ui_width();
int h = MUI_SBAR_H;
int vis = h - yoff; /* 收合上移:可见条高 = 28-yoff(其余行=App 内容,由 launcher 保留) */
if (vis <= 0) return; /* 完全收合由 statusbar_collapsed_render(提示线)处理 */
/* 整条清底(仅可见条高 [0,vis) 行):覆盖 app 可能画进顶行的内容(z 最顶)。仅系统进程渲染本函数。 */
meow_ui_fill_rect(0, 0, w, vis, th->bg);
/* 文字垂直锚 y:(栏高-行高)/2 居中;下限 1 防极端行高 > 栏高时负 y 溢出顶端(不越界优先)。
* 收合上移:内容整体上移 yoff 像素并裁顶(meow_font/图标 blit 已带 y<0 边界钳制,安全)。 */
int ty = (h - meow_ui_text_height(MUI_SBAR_FONT)) / 2;
if (ty < 1) ty = 1;
ty -= yoff;
int ycy = MEOW_SB_ICON_CY - yoff; /* 图标 ink 垂直中心随上移 */
/* 时钟(左):每秒现取 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);
/* 右侧 MD3 图标簇(从右端 w-8 往左)。主项色 th->fg;次级(蓝牙/弱信号)用 on_surface_variant。
* 亮度「可选」:仅在右簇左缘与时钟右缘相距足够(MEOW_SB_CLK_MARGIN)时画,避免与时钟叠字。 */
uint32_t col = th->fg, col2 = th->dim; /* col=主色;col2=次要色(蓝牙/分隔用 on_surface_variant=dim 近似) */
int xr = w - 8;
char val[16];
if (g_sys.batt >= 0) { /* 电量:图标+%(未知不画)——最右项 */
snprintf(val, sizeof val, "%d%%", g_sys.batt);
sb_r_place(&xr, MEOW_SB_ICON_BATT, val, col, MEOW_SB_ICON_INK, ty, ycy);
}
snprintf(val, sizeof val, "%d%%", g_sys.vol);
sb_r_place(&xr, MEOW_SB_ICON_VOL, val, col, MEOW_SB_ICON_INK, ty, ycy); /* 音量:图标+% */
if (g_sys.bt_seen && g_sys.bt_on) { /* 蓝牙:仅图标(上电即显示,连接态占位;多态留后续) */
sb_r_place(&xr, MEOW_SB_ICON_BT_CONN, NULL, col2, MEOW_SB_ICON_INK, ty, ycy);
}
if (g_sys.wifi_on) { /* WiFi:仅图标(关不画;开未连/分档) */
int wco = sb_wifi_icon();
int is_weak = (wco == MEOW_SB_ICON_WIFI_MEDIUM || wco == MEOW_SB_ICON_WIFI_WEAK);
sb_r_place(&xr, wco, NULL, col, is_weak ? 0 : MEOW_SB_ICON_INK, ty, ycy);
}
if (g_sys.brightness > 0) { /* 亮度:图标+%(>0 才显示;与时钟不重叠才画) */
int bl = (g_sys.brightness > 100) ? 100 : g_sys.brightness;
snprintf(val, sizeof val, "%d%%", bl);
int bw = MEOW_SB_ICON_SIZE + MEOW_SB_TGAP + meow_ui_text_width(val, MUI_SBAR_FONT) + MEOW_SB_XGAP;
if (xr - bw > 8 + meow_ui_text_width(timebuf, MUI_SBAR_FONT) + MEOW_SB_CLK_MARGIN)
sb_r_place(&xr, MEOW_SB_ICON_BRIGHT, val, col, MEOW_SB_ICON_INK, ty, ycy);
}
/* 分隔线(栏内最底 1px,主题次要色);上移后仍在可见条高内;内容 inset=statusbar_height() 从该线之下即干净 */
int dy = h - 1 - yoff;
if (dy >= 0 && dy < vis) meow_ui_fill_rect(0, dy, w, 1, th->dim);
g_statusbar_force = 0;
}