MeowOS for HiBy R3 Pro II — 公共发布版本(历史已重置)

• 全新单提交:清除含敏感信息的旧历史(sudo 口令、内网 IP、SSH 密钥路径、热点 SSID、设备 MAC)
• 本地开发文档/测试记录/构建脚本全部移入 gitignored 的 devdocs/ 与 scripts/build/(不入库)
• 保留:MeowOS 源码(launcher/watchdog/bootmenu/meow_svcd/SDK)、项目规范文档、mips-repo
增补组件:SDK 统一日志+通知 API、meow_svcd 服务管理器、mips-repo 软件仓库
This commit is contained in:
MeowOS Dev committed 2026-08-30 01:12:34 +08:00
commit 245bee2946
136 files changed
+32730

No files matched your search

+650
View File
@@ -0,0 +1,650 @@
/*
* meow_ui_gfx.c — meow_ui 渲染后端(B1.0+B1.1)。
*
* 独立实现:自建 fb 上下文(自 mmap /dev/fb0、自建 back_buf、自实现脏区+present),
* 不复用 launcher 的全局 g_dirty/fb(launcher 与 app 是不同进程)。机制参考
* MEOW_APP_SDK.md §1/§2 与 launcher 的 M2.16 无闪烁地基,代码全部独立书写。
*
* 抽象:所有绘制目标 = 离屏 back_buf(g_fb);meow_ui_flush() 时单次原子
* vsync+blit 到 fb0(真实设备),或写出 PPM 截图(宿主/qemu 离屏,MEOW_UI_DUMP)。
*/
#include "meow_ui.h"
#include "meow_ui_internal.h"
#include "meow_ui_sys.h"
#include "../meow_launcherd/meow_font.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
/* 本设备实测 ioctl 号(勿与通用头交换) */
#define FBIOGET_VSCREENINFO 0x4600
#define FBIOGET_FSCREENINFO 0x4602
#define FBIO_WAITFORVSYNC 0x4620
#define FB_DEV "/dev/fb0"
#define MEOW_FONT_PNG_CARD "/usr/data/mnt/sd_0/MeowAPPs/fonts/font.png"
#define MEOW_FONT_IDX_CARD "/usr/data/mnt/sd_0/MeowAPPs/fonts/font.idx"
#define MEOW_FONT_PNG_SYS "/usr/share/meowos/font/font.png"
#define MEOW_FONT_IDX_SYS "/usr/share/meowos/font/font.idx"
#define MUI_DEFAULT_W 480
#define MUI_DEFAULT_H 720
/* 脏矩形上限(超出合并为全屏) */
#define MUI_DIRTY_MAX 12
struct mu_rect { int x, y, w, h; };
/* ---- fb 上下文 ---- */
static unsigned char *g_back_buf = NULL; /* 离屏(所有绘制目标) */
static unsigned char *g_fb_mem = NULL; /* 真实 mmap 帧缓冲(present 落点;NULL=离屏) */
static unsigned char *g_fb = NULL; /* 绘制指针 = g_back_buf */
static int g_fb_fd = -1;
static int g_xres = MUI_DEFAULT_W, g_yres = MUI_DEFAULT_H;
static int g_bpp = 16;
static int g_line = 0; /* 每行字节数 */
static int g_init_ok = 0;
static int g_mfont_ready = 0;
static int g_dirty[/*MUI_DIRTY_MAX*/12][4]; /* [x,y,w,h] */
static int g_ndirty = 0;
static int g_dirty_full = 1;
static char *g_dump_path = NULL;
/* ---- 帧率调度(A-1/A-2:从 launcher M2.16 迁入,刷新率调度归口 SDK)----
* meow_ui_flush()/present() 的 fps 档位与自适应降档 governor。launcher 不再自带
* 一套帧率门控;launcher 与 app 统一走这里 → 全局观感/刷新率一致。
* 设置接口:meow_ui_set_refresh_rate()/meow_ui_set_refresh_always_max()。 */
static int g_refresh_rate_cfg = 60; /* 配置目标档 60/30/15(上限) */
static int g_eff_rate = 60; /* 当前有效帧率(governor 可降档) */
static int g_refresh_always_max = 0; /* 1=恒 max 不降档 */
static unsigned long long g_last_present_ms = 0;
static unsigned long long g_frames_presented = 0;
static unsigned long long g_present_cost_sum = 0;
static int g_present_cost_n = 0;
static int g_gov_low = 0;
/* 每档上限(refresh_always_max=1 时恒 max;0 时 governor 可在 [15,30,60] 内降/升档)。 */
static int rate_bump(int r) { return (r == 15) ? 30 : (r == 30) ? 60 : 60; }
static int rate_drop(int r) { return (r == 60) ? 30 : (r == 30) ? 15 : 15; }
/* 自适应降档 governor:统计每帧 present 耗时;连续 12 帧平均超预算 → 降一档;
* 连续 4 批(≈48 帧)平均远低于预算 → 升一档。refresh_always_max=1 时恒 max 不降。 */
static void refresh_governor(unsigned long long cost_ms, int period)
{
if (g_refresh_always_max || g_eff_rate <= 0) return;
g_present_cost_sum += cost_ms; g_present_cost_n++;
if (g_present_cost_n < 12) return;
unsigned long long avg = g_present_cost_sum / (unsigned int)g_present_cost_n;
if (avg > (unsigned long long)period) {
if (g_eff_rate > 15) g_eff_rate = rate_drop(g_eff_rate);
g_gov_low = 0;
} else if (avg * 2 < (unsigned long long)period) {
if (++g_gov_low >= 4) {
int nr = rate_bump(g_eff_rate);
if (nr <= g_refresh_rate_cfg) g_eff_rate = nr;
g_gov_low = 0;
}
} else g_gov_low = 0;
g_present_cost_sum = 0; g_present_cost_n = 0;
}
/* ---- 工具 ---- */
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;
}
uint64_t meow_ui_time_ms(void) { return now_ms(); }
static uint16_t rgb565(uint32_t c)
{
return (uint16_t)(((c >> 8) & 0xF800) | ((c >> 5) & 0x07E0) | ((c >> 3) & 0x001F));
}
static uint8_t ex5(uint16_t v) { return (uint8_t)(((v) << 3) | ((v) >> 2)); } /* 5→8 */
static uint8_t ex6(uint16_t v) { return (uint8_t)(((v) << 2) | ((v) >> 4)); } /* 6→8 */
static int rect_hit(int ax, int ay, int aw, int ah, int bx, int by, int bw, int bh)
{
return ax < bx + bw && ax + aw > bx && ay < by + bh && ay + ah > by;
}
/* ================= 脏区追踪 ================= */
static void dirty_full(void) { g_dirty_full = 1; g_ndirty = 0; }
static void dirty_reset(void) { g_ndirty = 0; g_dirty_full = 0; }
static void dirty_add(int x, int y, int w, int h)
{
if (g_dirty_full) return;
if (w <= 0 || h <= 0) return;
if (x < 0) { w += x; x = 0; }
if (y < 0) { h += y; y = 0; }
if (x >= g_xres || y >= g_yres) return;
if (x + w > g_xres) w = g_xres - x;
if (y + h > g_yres) h = g_yres - y;
if (w <= 0 || h <= 0) return;
for (int i = 0; i < g_ndirty; i++) {
if (rect_hit(g_dirty[i][0], g_dirty[i][1], g_dirty[i][2], g_dirty[i][3], x, y, w, h)) {
int nx1 = g_dirty[i][0] < x ? g_dirty[i][0] : x;
int ny1 = g_dirty[i][1] < y ? g_dirty[i][1] : y;
int nx2 = (g_dirty[i][0] + g_dirty[i][2]) > (x + w) ? (g_dirty[i][0] + g_dirty[i][2]) : (x + w);
int ny2 = (g_dirty[i][1] + g_dirty[i][3]) > (y + h) ? (g_dirty[i][1] + g_dirty[i][3]) : (y + h);
g_dirty[i][0] = nx1; g_dirty[i][1] = ny1;
g_dirty[i][2] = nx2 - nx1; g_dirty[i][3] = ny2 - ny1;
return;
}
}
if (g_ndirty >= MUI_DIRTY_MAX) { dirty_full(); return; }
g_dirty[g_ndirty][0] = x; g_dirty[g_ndirty][1] = y;
g_dirty[g_ndirty][2] = w; g_dirty[g_ndirty][3] = h;
g_ndirty++;
}
void meow_gfx_mark_dirty(int x, int y, int w, int h) { dirty_add(x, y, w, h); }
void meow_gfx_mark_full(void) { dirty_full(); }
/* launcher(系统组件)与 SDK 共用同一 back_buf:launcher 的 fb 绘制指针指向它,
* 从而 launcher 与 app 统一渲染栈(同一离屏 + 同一 present)。 */
unsigned char *meow_ui_back_buf(void) { return g_back_buf; }
/* ================= 像素写入 ================= */
static void px_write_rgb565(int x, int y, uint16_t c16)
{
*(uint16_t *)(g_fb + (size_t)y * g_line + (size_t)x * 2) = c16;
}
static uint16_t px_read_rgb565(int x, int y)
{
return *(uint16_t *)(g_fb + (size_t)y * g_line + (size_t)x * 2);
}
static void put_px(int x, int y, uint32_t color)
{
if (x < 0 || x >= g_xres || y < 0 || y >= g_yres) return;
if (g_bpp == 16) {
px_write_rgb565(x, y, rgb565(color));
} else {
unsigned char *p = g_fb + (size_t)y * g_line + (size_t)x * 4;
*(uint32_t *)p = (color << 8) | 0xFF;
}
}
static void put_px_blend(int x, int y, uint32_t color, int alpha)
{
if (x < 0 || x >= g_xres || y < 0 || y >= g_yres) return;
uint16_t fg = rgb565(color);
if (g_bpp == 16) {
uint16_t bg = px_read_rgb565(x, y);
int ia = 255 - alpha;
unsigned r = ((((fg >> 11) & 0x1F) * alpha + ((bg >> 11) & 0x1F) * ia) / 255);
unsigned gg = ((((fg >> 5) & 0x3F) * alpha + ((bg >> 5) & 0x3F) * ia) / 255);
unsigned b = (((fg & 0x1F) * alpha + (bg & 0x1F) * ia) / 255);
px_write_rgb565(x, y, (uint16_t)((r << 11) | (gg << 5) | b));
} else {
unsigned char *p = g_fb + (size_t)y * g_line + (size_t)x * 4;
uint32_t bg = (*(uint32_t *)p) >> 8;
int ia = 255 - alpha;
unsigned r = ((((color >> 16) & 0xFF) * alpha + ((bg >> 16) & 0xFF) * ia) / 255);
unsigned gg = ((((color >> 8) & 0xFF) * alpha + ((bg >> 8) & 0xFF) * ia) / 255);
unsigned b = (((color & 0xFF) * alpha + (bg & 0xFF) * ia) / 255);
*(uint32_t *)p = (r << 24) | (gg << 16) | (b << 8) | 0xFF;
}
}
/* ================= 基本绘制 ================= */
void meow_ui_clear(uint32_t color)
{
if (!g_init_ok) return;
/* 快速整刷(16bit 用半字填充,可 memset 当色值由 32bit 拼接掩码) */
if (g_bpp == 16) {
uint16_t c16 = rgb565(color);
uint32_t c32 = (uint32_t)c16 | ((uint32_t)c16 << 16);
for (int y = 0; y < g_yres; y++) {
uint32_t *row = (uint32_t *)(g_fb + (size_t)y * g_line);
int n = g_xres / 2;
for (int i = 0; i < n; i++) row[i] = c32;
if (g_xres & 1) px_write_rgb565(g_xres - 1, y, c16);
}
} else {
for (int y = 0; y < g_yres; y++)
for (int x = 0; x < g_xres; x++) put_px(x, y, color);
}
dirty_full();
}
void meow_ui_fill_rect(int x, int y, int w, int h, uint32_t color)
{
if (!g_init_ok) return;
if (w <= 0 || h <= 0) return;
int x0 = x, y0 = y;
if (x < 0) { w += x; x = 0; }
if (y < 0) { h += y; y = 0; }
if (x >= g_xres || y >= g_yres) return;
if (x + w > g_xres) w = g_xres - x;
if (y + h > g_yres) h = g_yres - y;
if (w <= 0 || h <= 0) return;
if (g_bpp == 16) {
uint16_t c16 = rgb565(color);
for (int ry = y; ry < y + h; ry++) {
uint16_t *rp = (uint16_t *)(g_fb + (size_t)ry * g_line + (size_t)x * 2);
for (int rx = 0; rx < w; rx++) rp[rx] = c16;
}
} else {
for (int ry = y; ry < y + h; ry++)
for (int rx = x; rx < x + w; rx++) put_px(rx, ry, color);
}
dirty_add(x, y, w, h);
}
void meow_ui_put_px(int x, int y, uint32_t color)
{
if (!g_init_ok) return;
put_px(x, y, color);
dirty_add(x, y, 1, 1);
}
void meow_ui_draw_rect(int x, int y, int w, int h, uint32_t color)
{
if (w <= 0 || h <= 0) return;
meow_ui_fill_rect(x, y, w, 1, color);
meow_ui_fill_rect(x, y + h - 1, w, 1, color);
meow_ui_fill_rect(x, y, 1, h, color);
meow_ui_fill_rect(x + w - 1, y, 1, h, color);
}
/* 圆角矩形区域判定(含内缩) */
static int rr_inside(int px, int py, int x, int y, int w, int h, int r)
{
if (px < x || px >= x + w || py < y || py >= y + h) return 0;
if (r <= 0) return 1;
if (w < 2 * r || h < 2 * r) return 1; /* 半径过大退化为矩形 */
int cx1 = x + r, cy1 = y + r;
int cx2 = x + w - 1 - r, cy2 = y + h - 1 - r;
if (px < cx1 && py < cy1) return ((px - cx1) * (px - cx1) + (py - cy1) * (py - cy1)) <= r * r;
if (px > cx2 && py < cy1) return ((px - cx2) * (px - cx2) + (py - cy1) * (py - cy1)) <= r * r;
if (px < cx1 && py > cy2) return ((px - cx1) * (px - cx1) + (py - cy2) * (py - cy2)) <= r * r;
if (px > cx2 && py > cy2) return ((px - cx2) * (px - cx2) + (py - cy2) * (py - cy2)) <= r * r;
return 1;
}
static int rr_border(int px, int py, int x, int y, int w, int h, int r)
{
if (!rr_inside(px, py, x, y, w, h, r)) return 0;
int ir = r - 1; if (ir < 0) ir = 0;
if (w > 2 && h > 2 && rr_inside(px, py, x + 1, y + 1, w - 2, h - 2, ir)) return 0;
return 1;
}
void meow_gfx_fill_round(int x, int y, int w, int h, int r, uint32_t color)
{
if (!g_init_ok) return;
int x1 = x, y1 = y, x2 = x + w, y2 = y + h;
if (x1 < 0) x1 = 0; if (y1 < 0) y1 = 0;
if (x2 > g_xres) x2 = g_xres; if (y2 > g_yres) y2 = g_yres;
for (int py = y1; py < y2; py++)
for (int px = x1; px < x2; px++)
if (rr_inside(px, py, x, y, w, h, r)) put_px(px, py, color);
dirty_add(x1, y1, x2 - x1, y2 - y1);
}
void meow_ui_draw_rect_round(int x, int y, int w, int h, int r, uint32_t color)
{
if (!g_init_ok) return;
int x1 = x, y1 = y, x2 = x + w, y2 = y + h;
if (x1 < 0) x1 = 0; if (y1 < 0) y1 = 0;
if (x2 > g_xres) x2 = g_xres; if (y2 > g_yres) y2 = g_yres;
for (int py = y1; py < y2; py++)
for (int px = x1; px < x2; px++)
if (rr_border(px, py, x, y, w, h, r)) put_px(px, py, color);
dirty_add(x1, y1, x2 - x1, y2 - y1);
}
void meow_ui_fill_rect_alpha(int x, int y, int w, int h, uint32_t color, int alpha)
{
if (!g_init_ok) return;
if (alpha <= 0) return;
if (alpha >= 255) { meow_ui_fill_rect(x, y, w, h, color); return; }
if (w <= 0 || h <= 0) return;
if (x < 0) { w += x; x = 0; }
if (y < 0) { h += y; y = 0; }
if (x >= g_xres || y >= g_yres) return;
if (x + w > g_xres) w = g_xres - x;
if (y + h > g_yres) h = g_yres - y;
if (w <= 0 || h <= 0) return;
for (int ry = y; ry < y + h; ry++)
for (int rx = x; rx < x + w; rx++) put_px_blend(rx, ry, color, alpha);
dirty_add(x, y, w, h);
}
/* ================= 左滑返回视觉条(GESTURE_BACK §2.3) ================= */
#define MUI_HINT_H 5
static int g_hint_last = 0; /* 上次画过的条长(清条时只清到它,避免残留) */
void meow_ui_swipe_hint(int x)
{
if (!g_init_ok) return;
const meow_theme *th = meow_theme_get();
/* 进度条画在状态栏下沿(z 在状态栏之下,不被全局状态栏覆盖)。
* 有状态栏(非全屏)→ y = 状态栏高度;无状态栏(全屏/隐藏)→ y=0。 */
int hy = meow_ui_statusbar_height();
if (x > 0) {
if (x > g_xres) x = g_xres;
/* 往回滑(新长度 < 上次)时,先清掉「上次画到旧长度、但本次超出的部分」,
* 否则条只增不减(用户要求:右滑变长、左滑回缩实时可见)。 */
if (g_hint_last > x) meow_ui_fill_rect(0, hy, g_hint_last, MUI_HINT_H, th->bg);
meow_ui_fill_rect(0, hy, x, MUI_HINT_H, th->accent);
g_hint_last = x;
} else if (g_hint_last > 0) {
/* 手指回原点/抬手未达标/抬手达标:清掉此前画的条(填回背景色) */
meow_ui_fill_rect(0, hy, g_hint_last, MUI_HINT_H, th->bg);
g_hint_last = 0;
}
/* 不发 flush:由调用方 meow_ui_flush_region(0,0,宽,6) 只刷顶部条 */
}
/* ================= 5x7 回退点阵字(与 launcher/hello 一致的表,独立携带) ================= */
static const unsigned char mfont5x7[][8] = {
{0x0E,0x11,0x13,0x15,0x19,0x11,0x0E,0x00},{0x04,0x0C,0x04,0x04,0x04,0x04,0x0E,0x00},
{0x0E,0x11,0x01,0x02,0x04,0x08,0x1F,0x00},{0x1F,0x02,0x04,0x02,0x01,0x11,0x0E,0x00},
{0x02,0x06,0x0A,0x12,0x1F,0x02,0x02,0x00},{0x1F,0x10,0x1E,0x01,0x01,0x11,0x0E,0x00},
{0x06,0x08,0x10,0x1E,0x11,0x11,0x0E,0x00},{0x1F,0x01,0x02,0x04,0x08,0x08,0x08,0x00},
{0x0E,0x11,0x11,0x0E,0x11,0x11,0x0E,0x00},{0x0E,0x11,0x11,0x0F,0x01,0x02,0x0C,0x00},
{0x0E,0x11,0x11,0x1F,0x11,0x11,0x11,0x00},{0x1E,0x11,0x11,0x1E,0x11,0x11,0x1E,0x00},
{0x0E,0x11,0x10,0x10,0x10,0x11,0x0E,0x00},{0x1C,0x12,0x11,0x11,0x11,0x12,0x1C,0x00},
{0x1F,0x10,0x10,0x1E,0x10,0x10,0x1F,0x00},{0x1F,0x10,0x10,0x1E,0x10,0x10,0x10,0x00},
{0x0E,0x11,0x10,0x17,0x11,0x11,0x0F,0x00},{0x11,0x11,0x11,0x1F,0x11,0x11,0x11,0x00},
{0x0E,0x04,0x04,0x04,0x04,0x04,0x0E,0x00},{0x07,0x02,0x02,0x02,0x02,0x12,0x0C,0x00},
{0x11,0x12,0x14,0x18,0x14,0x12,0x11,0x00},{0x10,0x10,0x10,0x10,0x10,0x10,0x1F,0x00},
{0x11,0x1B,0x15,0x15,0x11,0x11,0x11,0x00},{0x11,0x19,0x15,0x13,0x11,0x11,0x11,0x00},
{0x0E,0x11,0x11,0x11,0x11,0x11,0x0E,0x00},{0x1E,0x11,0x11,0x1E,0x10,0x10,0x10,0x00},
{0x0E,0x11,0x11,0x11,0x15,0x12,0x0D,0x00},{0x1E,0x11,0x11,0x1E,0x14,0x12,0x11,0x00},
{0x0F,0x10,0x10,0x0E,0x01,0x01,0x1E,0x00},{0x1F,0x04,0x04,0x04,0x04,0x04,0x04,0x00},
{0x11,0x11,0x11,0x11,0x11,0x11,0x0E,0x00},{0x11,0x11,0x11,0x11,0x11,0x0A,0x04,0x00},
{0x11,0x11,0x11,0x15,0x15,0x1B,0x11,0x00},{0x11,0x11,0x0A,0x04,0x0A,0x11,0x11,0x00},
{0x11,0x11,0x11,0x0A,0x04,0x04,0x04,0x00},{0x1F,0x01,0x02,0x04,0x08,0x10,0x1F,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x00},
{0x04,0x04,0x04,0x04,0x04,0x00,0x04,0x00},{0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x00},
};
#define FONT5X7_N (sizeof(mfont5x7)/sizeof(mfont5x7[0]))
static int glyph_index(char c)
{
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'A' && c <= 'Z') return 10 + (c - 'A');
if (c >= 'a' && c <= 'z') return 10 + (c - 'a');
if (c == ' ') return 36;
if (c == '.') return 37;
if (c == '!') return 38;
if (c == '-' || c == '_') return 39;
return 36;
}
static void draw_glyph5x7(int x, int y, char c, int scale, uint32_t color)
{
const unsigned char *g = mfont5x7[glyph_index(c)];
for (int row = 0; row < 7; row++) {
unsigned char bits = g[row];
for (int col = 0; col < 5; col++) {
if (bits & (0x10 >> col)) {
for (int dy = 0; dy < scale; dy++)
for (int dx = 0; dx < scale; dx++)
put_px(x + col * scale + dx, y + row * scale + dy, color);
}
}
}
}
static void draw_text5x7(int x, int y, const char *s, int scale, uint32_t color)
{
while (*s) { draw_glyph5x7(x, y, *s, scale, color); x += 6 * scale; s++; }
}
/* ================= 文本(字库优先,回退点阵) ================= */
static int fallback_scale(int size) { return (size >= 16) ? 2 : 1; }
static void text_draw(int x, int y, const char *s, int size, uint32_t color)
{
if (!g_init_ok || !s) return;
if (g_mfont_ready && g_bpp == 16) {
meow_font_drawtext((uint16_t *)g_fb, x, y, s, rgb565(color), size);
int w = meow_font_width_text(s, size);
int hh = meow_font_line_height(size);
dirty_add(x, y, w, hh);
} else {
int sc = fallback_scale(size);
draw_text5x7(x, y, s, sc, color);
dirty_add(x, y, (int)strlen(s) * 6 * sc, 7 * sc);
}
}
void meow_ui_text(int x, int y, const char *utf8, int size, uint32_t color)
{ text_draw(x, y, utf8, size, color); }
void meow_ui_text_right(int right_x, int y, const char *utf8, int size, uint32_t color)
{ text_draw(right_x - meow_ui_text_width(utf8, size), y, utf8, size, color); }
void meow_ui_text_center(int cx, int y, const char *utf8, int size, uint32_t color)
{ text_draw(cx - meow_ui_text_width(utf8, size) / 2, y, utf8, size, color); }
int meow_ui_text_width(const char *utf8, int size)
{
if (!utf8) return 0;
if (g_mfont_ready) return meow_font_width_text(utf8, size);
return (int)strlen(utf8) * 6 * fallback_scale(size);
}
int meow_ui_text_height(int size)
{
if (g_mfont_ready) return meow_font_line_height(size);
return 7 * fallback_scale(size);
}
/* ================= RGBA blit(图片层用) ================= */
void meow_gfx_blit_rgba(int x, int y, int w, int h, const uint8_t *rgba, int alpha)
{
if (!g_init_ok || !rgba || w <= 0 || h <= 0 || alpha <= 0) return;
int x1 = x, y1 = y, x2 = x + w, y2 = y + h;
if (x1 < 0) x1 = 0; if (y1 < 0) y1 = 0;
if (x2 > g_xres) x2 = g_xres; if (y2 > g_yres) y2 = g_yres;
for (int py = y1; py < y2; py++) {
int sy = py - y;
for (int px = x1; px < x2; px++) {
int sx = px - x;
const uint8_t *p = rgba + ((size_t)sy * w + sx) * 4;
int a = (alpha * p[3]) / 255;
if (a <= 0) continue;
uint32_t c = ((uint32_t)p[0] << 16) | ((uint32_t)p[1] << 8) | (uint32_t)p[2];
if (a >= 255) put_px(px, py, c);
else put_px_blend(px, py, c, a);
}
}
dirty_add(x1, y1, x2 - x1, y2 - y1);
}
/* ================= present / 上屏 ================= */
static void dump_ppm(void)
{
if (!g_dump_path || !g_back_buf) return;
FILE *f = fopen(g_dump_path, "wb");
if (!f) return;
fprintf(f, "P6\n%d %d\n255\n", g_xres, g_yres);
for (int y = 0; y < g_yres; y++) {
for (int x = 0; x < g_xres; x++) {
unsigned char r, g, b;
if (g_bpp == 16) {
uint16_t px = *(uint16_t *)(g_back_buf + (size_t)y * g_line + (size_t)x * 2);
r = ex5((px >> 11) & 0x1F); g = ex6((px >> 5) & 0x3F); b = ex5(px & 0x1F);
} else {
uint32_t px = *(uint32_t *)(g_back_buf + (size_t)y * g_line + (size_t)x * 4);
r = (px >> 16) & 0xFF; g = (px >> 8) & 0xFF; b = px & 0xFF;
}
fputc(r, f); fputc(g, f); fputc(b, f);
}
}
fclose(f);
}
/* 顶部状态栏条是否被本次脏区触及(含整帧):触及或数据已变(force)才需要重画顶条。 */
static int statusbar_touched(void)
{
if (g_dirty_full) return 1;
for (int i = 0; i < g_ndirty; i++)
if (g_dirty[i][1] < MUI_SBAR_H) return 1; /* 脏矩形与顶条(前 MUI_SBAR_H 行)相交 */
return 0;
}
static void present(void)
{
if (!g_init_ok || !g_back_buf) return;
if (!(g_dirty_full || g_ndirty > 0)) return;
/* 帧率门控(A-1/A-2,从 launcher 迁入):present 距上次 ≥ period(1000/eff_rate) 才呈现;
* 未满周期则保留脏区(不 dirty_reset),下一轮再试。refresh_always_max=1 时跳过门控(恒满速)。 */
unsigned long long now = now_ms();
int period = (g_eff_rate > 0) ? (1000 / g_eff_rate) : 16;
if (!g_refresh_always_max) {
if (g_last_present_ms && now - g_last_present_ms < (unsigned long long)period) return;
}
/* 全局状态栏(A-0):上屏前把状态栏画进 back_buf 顶部 MUI_SBAR_H 行(z 最顶)。
* 全屏/隐藏时 active()==0 不画(沉浸),app 内容可占满整屏。触发条件:
* 数据已变(1s 轻刷/显隐切换) 或 顶条被 app 内容触及(含整帧清+绘,未 inset 时被顶条覆盖)。
* 无需每帧重画——app 局部刷新不触及顶条时,back_buf/screen 顶条保持不变,省一次 blit。 */
if (meow_ui_sys_statusbar_active() &&
(meow_ui_sys_statusbar_force() || statusbar_touched()))
meow_ui_sys_statusbar_render();
if (g_fb_mem) {
ioctl(g_fb_fd, FBIO_WAITFORVSYNC, 0); /* 等垂直同步 */
unsigned long long tbg = now_ms();
if (g_dirty_full) {
memcpy(g_fb_mem, g_back_buf, (size_t)g_line * (size_t)g_yres);
} else {
int bppb = g_bpp / 8;
for (int i = 0; i < g_ndirty; i++) {
int rw = g_dirty[i][2], ry = g_dirty[i][1];
for (int yy = 0; yy < g_dirty[i][3]; yy++) {
size_t off = (size_t)(ry + yy) * g_line + (size_t)g_dirty[i][0] * bppb;
memcpy(g_fb_mem + off, g_back_buf + off, (size_t)rw * bppb);
}
}
}
refresh_governor(now_ms() - tbg, period);
}
g_last_present_ms = now;
g_frames_presented++;
if (g_dump_path) dump_ppm();
dirty_reset();
}
void meow_ui_flush(void) { present(); }
void meow_ui_flush_region(int x, int y, int w, int h)
{ dirty_add(x, y, w, h); present(); }
/* ---- 帧率调度设置接口(launcher 与 app 共用;归口 PERF_SCHEDULER 显示刷新率子域)---- */
/* 设目标档位(60/30/15):同时把当前有效档对齐到目标(与 launcher settings_load 语义一致)。 */
void meow_ui_set_refresh_rate(int r)
{
if (r != 60 && r != 30 && r != 15) return;
g_refresh_rate_cfg = r;
g_eff_rate = r;
}
/* 1=恒 max 不降档(性能优先);0=允许 governor 在 [15,30,60] 内自适应降/升档。 */
void meow_ui_set_refresh_always_max(int on) { g_refresh_always_max = on ? 1 : 0; }
/* 当前有效帧率(governor 可能已降档;验证/诊断用)。 */
int meow_ui_effective_rate(void) { return g_eff_rate; }
/* 已呈现帧计数(验证 present 频率用)。 */
unsigned long long meow_ui_frames_presented(void) { return g_frames_presented; }
int meow_gfx_is_device(void) { return g_fb_mem != NULL; }
int meow_gfx_mfont_ready(void) { return g_mfont_ready; }
/* ================= 初始化/销毁 ================= */
static int load_font(void)
{
if (meow_font_load(MEOW_FONT_PNG_CARD, MEOW_FONT_IDX_CARD) == 0) return 1;
if (meow_font_load(MEOW_FONT_PNG_SYS, MEOW_FONT_IDX_SYS) == 0) return 1;
return 0;
}
int meow_ui_init(const meow_ui_opts *opts)
{
if (g_init_ok) return 0;
int fw = 0, fh = 0;
if (opts) { fw = opts->width; fh = opts->height; }
int fd = open(FB_DEV, O_RDWR);
if (fd >= 0) {
unsigned char vinfo[160], finfo[68];
memset(vinfo, 0, sizeof vinfo);
memset(finfo, 0, sizeof finfo);
if (ioctl(fd, FBIOGET_VSCREENINFO, vinfo) == 0 &&
ioctl(fd, FBIOGET_FSCREENINFO, finfo) == 0) {
g_xres = *(uint32_t *)(vinfo + 0);
g_yres = *(uint32_t *)(vinfo + 4);
g_bpp = *(uint32_t *)(vinfo + 24);
g_line = *(uint32_t *)(finfo + 44);
if (!g_line) g_line = g_xres * (g_bpp / 8);
if (g_bpp != 16 && g_bpp != 32) g_bpp = 16;
size_t maplen = (size_t)g_line * (size_t)g_yres;
void *m = mmap(NULL, maplen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (m != MAP_FAILED) { g_fb_mem = m; g_fb_fd = fd; }
else { close(fd); fd = -1; g_fb_mem = NULL; }
} else { close(fd); fd = -1; g_fb_mem = NULL; }
}
/* host/qemu 无 fb:使用缺省/覆写几何,走离屏(present 只 dump) */
if (g_fb_mem == NULL) {
if (g_xres <= 0 || g_xres > 4096) g_xres = MUI_DEFAULT_W;
if (g_yres <= 0 || g_yres > 4096) g_yres = MUI_DEFAULT_H;
if (g_bpp != 16 && g_bpp != 32) g_bpp = 16;
g_line = g_xres * (g_bpp / 8);
if (fd >= 0) { close(fd); fd = -1; }
}
if (fw > 0 && fw <= 4096) g_xres = fw;
if (fh > 0 && fh <= 4096) g_yres = fh;
g_line = g_xres * (g_bpp / 8);
g_back_buf = malloc((size_t)g_line * (size_t)g_yres);
if (!g_back_buf) { if (g_fb_mem) munmap(g_fb_mem, 0); if (fd >= 0) close(fd); return -1; }
g_fb = g_back_buf;
meow_font_set_fb((unsigned)g_xres, (unsigned)g_yres, (unsigned)g_line);
g_mfont_ready = load_font();
const char *dp = getenv("MEOW_UI_DUMP");
if (dp && *dp) g_dump_path = strdup(dp);
g_init_ok = 1;
dirty_full();
meow_ui_sys_init(); /* A-0:系统状态读取器首刷(电量/WiFi/蓝牙/音量),状态栏就绪 */
return 0;
}
void meow_ui_shutdown(void)
{
if (!g_init_ok) return;
meow_font_unload();
if (g_fb_mem) munmap(g_fb_mem, (size_t)g_line * (size_t)g_yres);
if (g_fb_fd >= 0) close(g_fb_fd);
if (g_back_buf) free(g_back_buf);
if (g_dump_path) { free(g_dump_path); g_dump_path = NULL; }
g_back_buf = NULL; g_fb = NULL; g_fb_mem = NULL;
g_fb_fd = -1; g_init_ok = 0; g_mfont_ready = 0;
dirty_reset();
}
int meow_ui_width(void) { return g_xres; }
int meow_ui_height(void) { return g_yres; }