820 lines
36 KiB
C
820 lines
36 KiB
C
/*
|
||
* 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/MeowOS/fonts/default/font.png"
|
||
#define MEOW_FONT_IDX_CARD "/usr/data/mnt/sd_0/MeowOS/fonts/default/font.idx"
|
||
#define MEOW_FONT_PNG_SYS "/usr/share/meowos/font/font.png"
|
||
#define MEOW_FONT_IDX_SYS "/usr/share/meowos/font/font.idx"
|
||
/* 默认字库(卡 MeowOS/fonts/default)与固件内置之外,launcher 会把「当前生效字体」
|
||
* 经环境变量下发给 app(见 meow_launcherd.c launch_app / meow_font_effective_paths):
|
||
* MEOW_FONT_PNG / MEOW_FONT_IDX = 绝对路径;MEOW_FONT_APP = <family> 或 "default"。
|
||
* SDK load_font 优先读 env(匹配系统 font.file),无则回退卡默认 → 固件。 */
|
||
|
||
#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;
|
||
|
||
/* ---- 状态栏「上收出屏只留提示线」(collapsed,基座)----
|
||
* g_sbar_collapsed = 目标态(1=上收);g_sbar_collapse_h = 当前实际上收偏移
|
||
* (0..MUI_SBAR_H:0=完全展开,MUI_SBAR_H=内容全部上移出屏、仅留提示线)。
|
||
* 基座让 meow_ui_statusbar_set_collapsed(on) 立即生效(true→只画提示线/内容上移);
|
||
* 过渡(每帧插值)留待后续代理或经 meow_ui_statusbar_animate(ms) 启用的简易线性逼近。 */
|
||
#define MUI_SBAR_HINT_H 6 /* 上收状态下顶部提示线高(px) */
|
||
static int g_sbar_collapsed = 0; /* 目标:1=上收 */
|
||
static int g_sbar_collapse_h = 0; /* 当前上收偏移(0..MUI_SBAR_H) */
|
||
static int g_sbar_collapse_anim_ms = 0; /* 过渡时长(0=瞬切);meow_ui_statusbar_animate 设置 */
|
||
static int g_sbar_collapse_from_h = 0; /* 本次过渡起点偏移 */
|
||
static unsigned long long g_sbar_collapse_start_ms = 0; /* 本次过渡起始(monotonic ms) */
|
||
|
||
/* ---- 字体档位(MEOW_FONT_TIER,基座存档 + 公共 get/set)----
|
||
* launcher 按 app.json "fonts":<档> 注入 MEOW_FONT_TIER=<0|1|2>;SDK 读入并以
|
||
* meow_font_set_tier/tier 暴露。真正按档装载大字库(GBK/Full)由后续 font 代理在
|
||
* meow_font.c / meow_gfx_load_multifont 挂钩实现——此处仅记录档位不换库。 */
|
||
static int g_font_tier = MEOW_FONT_TIER_COMMON;
|
||
|
||
/* ---- 帧率调度(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);
|
||
}
|
||
|
||
/* ================= 状态栏「上收出屏只留提示线」(collapsed,基座) ================= */
|
||
/* 每帧推进上收/恢复:向目标偏移(collapsed→MUI_SBAR_H、否则→0)线性逼近;
|
||
* 无过渡时长(g_sbar_collapse_anim_ms<=0)= 一步到位(基座默认)。动画插值骨架在此,
|
||
* 后续代理可换缓动/每帧步进。 */
|
||
static void statusbar_collapse_step(void)
|
||
{
|
||
int tgt = g_sbar_collapsed ? MUI_SBAR_H : 0;
|
||
if (g_sbar_collapse_h == tgt) return;
|
||
if (g_sbar_collapse_anim_ms <= 0) { g_sbar_collapse_h = tgt; return; }
|
||
unsigned long long now = now_ms();
|
||
unsigned long long el = now - g_sbar_collapse_start_ms;
|
||
int span = g_sbar_collapse_anim_ms;
|
||
if ((int)el >= span) { g_sbar_collapse_h = tgt; return; }
|
||
int delta = tgt - g_sbar_collapse_from_h;
|
||
g_sbar_collapse_h = g_sbar_collapse_from_h +
|
||
(int)((long long)delta * (long long)el / (long long)span);
|
||
}
|
||
|
||
/* 上收中是否需要逐帧重画(动画未达目标位置时置位,强制每帧推进)。 */
|
||
static int statusbar_collapse_in_transition(void)
|
||
{
|
||
int tgt = g_sbar_collapsed ? MUI_SBAR_H : 0;
|
||
return g_sbar_collapse_h != tgt;
|
||
}
|
||
|
||
/* 上收状态栏:内容整体上移出屏(不画时钟/电量),仅在顶部画一条「提示线」。
|
||
* 与 meow_ui_swipe_hint(左滑返回进度条,5px,动态)不同:此线是「状态栏可下拉还原」的
|
||
* 常驻提示(主题强调色下边缘)。原状态栏整块清回主题背景,避免残影(app inset 之内)。 */
|
||
static void statusbar_collapsed_render(int h)
|
||
{
|
||
(void)h;
|
||
const meow_theme *th = meow_theme_get();
|
||
int w = meow_ui_width();
|
||
/* h 越大内容越贴顶;基座:折叠态整块清底 + 顶部提示线(提示线下缘=主题 accent) */
|
||
meow_ui_fill_rect(0, 0, w, MUI_SBAR_H, th->bg);
|
||
meow_ui_fill_rect(0, MUI_SBAR_HINT_H - 1, w, 1, th->accent);
|
||
}
|
||
|
||
/* ---- 公共 API(基座实现;动画插值骨架见 statusbar_collapse_step)---- */
|
||
void meow_ui_statusbar_set_collapsed(int on)
|
||
{
|
||
int tgt = on ? 1 : 0;
|
||
if (tgt == g_sbar_collapsed) return;
|
||
g_sbar_collapsed = tgt;
|
||
g_sbar_collapse_from_h = g_sbar_collapse_h;
|
||
g_sbar_collapse_start_ms = now_ms();
|
||
dirty_full(); /* 顶栏显隐/上收变化 → 全帧重画 */
|
||
}
|
||
|
||
int meow_ui_statusbar_collapsed(void) { return g_sbar_collapsed; }
|
||
|
||
void meow_ui_statusbar_animate(int ms)
|
||
{
|
||
g_sbar_collapse_anim_ms = (ms > 0) ? ms : 0; /* 0=瞬切(基座默认) */
|
||
}
|
||
|
||
int meow_ui_statusbar_hotzone(void)
|
||
{
|
||
/* 非全屏=整条状态栏可视;全屏时缩小到 MEOW_SBAR_HOTZONE_FS(保留一处下拉入口)。
|
||
* 上收(collapsed、非全屏)时热区=提示线下缘高度。全屏态由 meow_ui_sys 维护。 */
|
||
if (meow_ui_sys_statusbar_active())
|
||
return g_sbar_collapsed ? MUI_SBAR_HINT_H : MUI_SBAR_H;
|
||
return MEOW_SBAR_HOTZONE_FS;
|
||
}
|
||
|
||
/* 顶部状态栏条是否被本次脏区触及(含整帧):触及或数据已变(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)+ 上收(collapsed)基座:上屏前把状态栏画进 back_buf 顶部 MUI_SBAR_H 行。
|
||
* 全屏/隐藏时 active()==0 不画(沉浸),app 内容可占满整屏。触发条件:
|
||
* 数据已变(1s 轻刷/显隐切换) / 顶条被 app 内容触及 / 上收动画未到位(逐帧推进)。
|
||
* 展开态仍走 meow_ui_sys_statusbar_render()(meow_ui_sys.c 不改,留给后续代理);
|
||
* 上收折叠态(collapse_h>=MUI_SBAR_H)由基座只画提示线(内容上移出屏)。 */
|
||
if (meow_ui_sys_statusbar_active() &&
|
||
(meow_ui_sys_statusbar_force() || statusbar_touched() ||
|
||
statusbar_collapse_in_transition())) {
|
||
statusbar_collapse_step();
|
||
if (g_sbar_collapse_h >= MUI_SBAR_H)
|
||
statusbar_collapsed_render(g_sbar_collapse_h);
|
||
else
|
||
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; }
|
||
|
||
/* ---- 字体档位(公共 API,基座定义)----
|
||
* 注意:meow_font_tier()/meow_font_set_tier() 已在 meow_ui_gfx.c 定义(符号唯一),
|
||
* 后续 font 代理勿在 meow_font.c 重复定义;可在 meow_font.c 实现「按档装载大字库」并在
|
||
* meow_font_set_tier 的钩子处唤起(见下)。 */
|
||
int meow_font_tier(void) { return g_font_tier; }
|
||
|
||
int meow_font_set_tier(int tier)
|
||
{
|
||
if (tier < MEOW_FONT_TIER_COMMON) tier = MEOW_FONT_TIER_COMMON;
|
||
if (tier > MEOW_FONT_TIER_FULL) tier = MEOW_FONT_TIER_FULL;
|
||
if (tier == g_font_tier) return g_font_tier;
|
||
g_font_tier = tier;
|
||
/* TBD(基座):真正按档装载大字库(meow_font_load_tier 或 config_multifont 扩展)由
|
||
* 后续 font 代理在 meow_font.c 实现;此处仅记录档位 + 标记重载(下一轮 load 读 tier)。 */
|
||
return g_font_tier;
|
||
}
|
||
|
||
/* ================= 初始化/销毁 ================= */
|
||
/* 字库加载优先级(M2.x):
|
||
* 1) 环境变量 MEOW_FONT_PNG/MEOW_FONT_IDX(launcher 注入的「当前生效字体」= 系统 font.file)
|
||
* —— app 未自带字库(§5.7 load_handle 未实现)时跟随系统 font.file;
|
||
* 2) 卡默认 MeowOS/fonts/default/font.png(不再硬编码 MeowAPPs/fonts);
|
||
* 3) 固件 /usr/share/meowos/font/font.png。
|
||
* 全失败 → g_mfont_ready=0,text_draw 回退 5x7 点阵(系统不死)。 */
|
||
static int load_font(void)
|
||
{
|
||
/* 字体档位(MEOW_FONT_TIER env,由 launcher 按 app.json "fonts" 注入):
|
||
* 0=默认4K常用 1=大字库GBK 2=Full。基座只读入存档 + 对齐公共 get/set;
|
||
* 真正按档装载大字库(GBK/Full 的字库路径/面)由后续 font 代理在 meow_font.c /
|
||
* meow_gfx_load_multifont 挂钩实现——此处仍先装载默认(tier0)字库,档位仅记录。 */
|
||
const char *etr = getenv("MEOW_FONT_TIER");
|
||
if (etr && etr[0]) {
|
||
g_font_tier = atoi(etr);
|
||
if (g_font_tier < MEOW_FONT_TIER_COMMON) g_font_tier = MEOW_FONT_TIER_COMMON;
|
||
if (g_font_tier > MEOW_FONT_TIER_FULL) g_font_tier = MEOW_FONT_TIER_FULL;
|
||
}
|
||
const char *ep = getenv("MEOW_FONT_PNG");
|
||
const char *ei = getenv("MEOW_FONT_IDX");
|
||
if (ep && ep[0] && ei && ei[0] && meow_font_load(ep, ei) == 0) return 1;
|
||
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;
|
||
}
|
||
|
||
/* M2.5x 多字体:读 launcher 注入的环境变量,构建 6 字符类 → 字库的映射(同 family 去重、
|
||
* ≤4 套上限由 meow_font_config_multifont 统一处理)。launcher 注入(见 meow_launcherd.c
|
||
* launch_app):MEOW_MULTI_FONT=0/1;MEOW_FONT_PNG/IDX=全局(保留);每类
|
||
* MEOW_FONT_CLS_<X>_PNG/IDX(命中则覆盖全局;未注入 = 该类跟随全局)。
|
||
* 仅当全局字库已装载(g_mfont_ready)才配置;关/无 env → 单字体,零额外开销。 */
|
||
static void meow_gfx_load_multifont(void)
|
||
{
|
||
static const char *cls_env[MEOW_FCLS_COUNT][2] = {
|
||
{ "MEOW_FONT_CLS_CJK_PNG", "MEOW_FONT_CLS_CJK_IDX" },
|
||
{ "MEOW_FONT_CLS_CJK_SYM_PNG", "MEOW_FONT_CLS_CJK_SYM_IDX" },
|
||
{ "MEOW_FONT_CLS_NUM_PNG", "MEOW_FONT_CLS_NUM_IDX" },
|
||
{ "MEOW_FONT_CLS_LATIN_PNG", "MEOW_FONT_CLS_LATIN_IDX" },
|
||
{ "MEOW_FONT_CLS_LATIN_SYM_PNG", "MEOW_FONT_CLS_LATIN_SYM_IDX" },
|
||
{ "MEOW_FONT_CLS_OTHER_PNG", "MEOW_FONT_CLS_OTHER_IDX" },
|
||
};
|
||
const char *emf = getenv("MEOW_MULTI_FONT");
|
||
int on = emf && emf[0] == '1';
|
||
const char *gp = getenv("MEOW_FONT_PNG");
|
||
const char *gi = getenv("MEOW_FONT_IDX");
|
||
meow_font_class_cfg cfg[MEOW_FCLS_COUNT];
|
||
memset(cfg, 0, sizeof cfg);
|
||
for (int c = 0; c < MEOW_FCLS_COUNT; c++) {
|
||
const char *p = getenv(cls_env[c][0]);
|
||
const char *idx = getenv(cls_env[c][1]);
|
||
if (p && p[0] && idx && idx[0]) { cfg[c].png = p; cfg[c].idx = idx; }
|
||
}
|
||
int distinct = 0, truncated = 0;
|
||
if (meow_font_config_multifont(on, gp, gi, cfg, &distinct, &truncated) != 0)
|
||
meow_log(MEOW_LOG_WARN, "multi_font: config failed, single-font fallback");
|
||
else if (truncated)
|
||
meow_log(MEOW_LOG_WARN, "multi_font: >%d distinct fonts -> capped (more memory!)", distinct);
|
||
}
|
||
|
||
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();
|
||
if (g_mfont_ready) meow_gfx_load_multifont(); /* M2.5x 多字体(读 env;关/无 env = 单字体) */
|
||
|
||
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/蓝牙/音量),状态栏就绪 */
|
||
/* 全屏预置(基座):launcher 按 app.json "fullscreen" 注入 MEOW_FULLSCREEN=1,SDK 据此
|
||
* 预先设全屏(隐藏整条状态栏/沉浸,statusbar_active()==0、statusbar_height()==0)。
|
||
* 未注入/非"1"=非全屏(默认)。放在 sys_init 后使首帧即为全屏态。 */
|
||
const char *efs = getenv("MEOW_FULLSCREEN");
|
||
if (efs && efs[0] == '1') meow_ui_set_fullscreen(1);
|
||
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; }
|