/* fb_compositor.c — MeowOS launcher 拆分模块(LAUNCHER_SPLIT_PLAN 拆分)。*/ /* 本文件由拆分工具从 meow_launcherd.c 按职责搬移;逻辑与原文 0-diff(仅 static 可见性按跨模块需求调整)。*/ #include "meow_launcher_internal.h" unsigned char *fb; /* 绘制目标指针 = SDK 的 back_buf(A-1/A-2:launcher 与 app 统一渲染栈) */ static unsigned char *fb_mem = NULL; /* 真实 mmap 帧缓冲(播放器帧备份/恢复源 + app 帧读回 + 双缓冲 yoffset 用) */ int fb_xres, fb_yres, fb_line, fb_bpp; int fb_fd = -1; static unsigned int fb_yres_virtual = 0; /* virtual height (double-buffer = 2*yres) */ static unsigned int fb_active_yoffset = 0; /* scanline of the displayed buffer (0 or yres) */ static int fb_orig_yoffset = -1; /* player's yoffset at menu open (saved) */ static unsigned char *fb_player_backup = NULL; static size_t fb_player_backup_len = 0; /* ---- A-1/A-2:launcher 用 SDK 渲染栈 ---- * fb 指向 SDK 的 back_buf(meow_ui_back_buf()),所有绘制(put_px/meow_font/dim_screen/ * draw_menu/statusbar/电源菜单/确认框)画进 SDK 的离屏;呈现统一走 meow_ui_flush()(= SDK present, * 内含 vsync + 帧率门控 + 原子 blit 脏区)。launcher 不再自建 back_buf/meow_present/帧率门控。 * fb_mem 仍保留 launcher 自有的全虚拟 mmap:仅用于播放器帧备份/恢复/双缓冲 yoffset 切换 * (fb_backup_player/fb_norm_buf0/fb_restore_player/fb_pan_buf0_force)与 app 帧读回 * (pm_capture_app_frame)——这些是「显示 buffer/页码切换」,与「present 上屏」不同层,不动。 */ int back_buf_ok = 0; /* fb 是否指向 SDK back_buf(1=可经 SDK present 上屏) */ int fb_open(void) { int fd = open(FB_DEV, O_RDWR); if (fd < 0) { logmsg("fb open failed: %s", strerror(errno)); return -1; } 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) { logmsg("fb ioctl failed"); close(fd); return -1; } fb_xres = *(uint32_t *)(vinfo + 0); fb_yres = *(uint32_t *)(vinfo + 4); fb_yres_virtual = *(uint32_t *)(vinfo + 12); fb_active_yoffset = *(uint32_t *)(vinfo + 20); fb_bpp = *(uint32_t *)(vinfo + 24); fb_line = *(uint32_t *)(finfo + 44); if (!fb_line) fb_line = fb_xres * (fb_bpp / 8); /* v6.6: fb0 is DOUBLE-BUFFERED (verified yres_virtual=1440=2*720, * smem_len=2 frames, ypanstep=1). The displayed buffer is selected by * yoffset (FBIOPAN_DISPLAY 0x4606 verified to flip it). Map the FULL * virtual framebuffer so we can read/write both buffers for the * backup/restore below; mmapping only one frame would let the menu * land on the buffer the panel is NOT showing. */ if (fb_yres_virtual < fb_yres) fb_yres_virtual = fb_yres; /* single buffer */ fb_mem = mmap(NULL, (size_t)fb_line * fb_yres_virtual, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (fb_mem == MAP_FAILED) { logmsg("fb mmap failed: %s (xres=%d yres=%d bpp=%d line=%d size=%d)", strerror(errno), fb_xres, fb_yres, fb_bpp, fb_line, fb_line * fb_yres_virtual); close(fd); return -1; } fb_fd = fd; /* A-1/A-2:launcher 用 SDK 渲染栈 —— fb 指向 SDK 的 back_buf(统一离屏),present 统一走 * meow_ui_flush()(SDK present:vsync + 帧率门控 + 原子 blit 脏区)。不再自建 back_buf。 * fb_mem(本函数 mmap 的全虚拟 fb)仅保留给播放器帧备份/恢复 + yoffset 切换 + app 帧读回。 */ fb = meow_ui_back_buf(); if (fb) { back_buf_ok = 1; meow_dirty_full(); logmsg("fb: %dx%d bpp=%d line=%d vres=%u yoffset=%u (A-1/A-2 SDK back_buf, meow_ui_flush)", fb_xres, fb_yres, fb_bpp, fb_line, fb_yres_virtual, fb_active_yoffset); } else { fb = fb_mem; back_buf_ok = 0; logmsg("fb: no SDK back_buf -> direct fb write (present disabled)"); } /* NOTE: the panel is NOT unblanked here anymore — the player may still * be running at this point (stop_player() comes after fb_open) and its * screen-off state machine would re-blank the panel behind the menu. * unblank_panel() runs after stop_player() (v6.2 hotfix) so the frozen * player cannot re-blank and the menu stays visible. */ return fd; } /* ---- double-buffer handling (v6.6) -------------------------------- * fb0 is double-buffered and the DISPLAYED buffer is chosen by fb_var's * yoffset (FBIOPAN_DISPLAY flips it). The player page-flips via yoffset (its * power/screen-off redraw leaves yoffset on the back buffer), so the menu * MUST be drawn to the buffer the panel actually shows — otherwise the menu * is invisible and the screen freezes on the player's last frame (the v6.5 * bug). Strategy: snapshot the player's displayed frame into a heap backup, * pan the display to buffer0 (what the launcher and all apps draw to — apps * like hello.c mmap exactly one frame at offset 0 and never pan), draw the * menu/app-list to buffer0, and on session end copy the backup back to the * player's buffer and pan to the original yoffset so a SIGSTOPped player that * does NOT redraw (event-driven, ANALYSIS §18.15) still shows its own frame. */ /* Snapshot the player's currently-displayed frame into a heap backup. */ void fb_backup_player(void) { if (fb_yres_virtual <= fb_yres) { fb_orig_yoffset = -1; return; } /* single buffer */ if (fb_active_yoffset >= fb_yres_virtual) fb_active_yoffset = 0; fb_orig_yoffset = (int)fb_active_yoffset; fb_player_backup_len = (size_t)fb_line * fb_yres; fb_player_backup = malloc(fb_player_backup_len); if (!fb_player_backup) { logmsg("fb backup malloc failed (len=%zu)", fb_player_backup_len); fb_orig_yoffset = -1; fb_player_backup_len = 0; return; } memcpy(fb_player_backup, fb_mem + (size_t)fb_active_yoffset * fb_line, fb_player_backup_len); logmsg("fb backup: player frame @ yoffset=%u (buffer %u) -> heap", fb_active_yoffset, fb_yres ? fb_active_yoffset / fb_yres : 0); } /* Pan the display to buffer0 (yoffset=0). buffer0 is what this launcher and * every app draw to (apps mmap one frame at offset 0 and never pan), so the * panel must show buffer0 for the menu and apps to be visible. */ void fb_norm_buf0(void) { if (fb_fd < 0 || fb_yres_virtual <= fb_yres) { fb_active_yoffset = 0; return; } if (fb_active_yoffset == 0) return; /* already showing buffer0 */ unsigned char v[160]; memset(v, 0, sizeof v); if (ioctl(fb_fd, FBIOGET_VSCREENINFO, v) < 0) return; *(uint32_t *)(v + 16) = 0; /* xoffset */ *(uint32_t *)(v + 20) = 0; /* yoffset -> buffer0 */ ioctl(fb_fd, FBIOPAN_DISPLAY, v); fb_active_yoffset = 0; logmsg("fb pan -> buffer0 (yoffset=0)"); } /* M2.8 Bug 3-residual 根因:fb_norm_buf0 在缓存 fb_active_yoffset==0 时短路提前返回、 * 不再重发 FBIOPAN_DISPLAY。但 FBIOBLANK(4) 下电 -> FBIOBLANK(0) 上电后,LCDC 的扫描 * 地址可能已复位到非 buffer0 / 失效位置,而缓存仍是 0 —— 于是真正的 re-pan 从未发出, * 唤醒后仍是黑屏/旧帧。本函数无条件重发 pan 到 buffer0(同 yoffset 的重复 pan 是硬件 * no-op,无频闪),供唤醒/重绘路径调用,确保重画的界面一定落在被显示的缓冲。 */ void fb_pan_buf0_force(void) { if (fb_fd < 0 || fb_yres_virtual <= fb_yres) { fb_active_yoffset = 0; return; } unsigned char v[160]; memset(v, 0, sizeof v); if (ioctl(fb_fd, FBIOGET_VSCREENINFO, v) < 0) return; /* read current vinfo */ *(uint32_t *)(v + 16) = 0; /* xoffset */ *(uint32_t *)(v + 20) = 0; /* yoffset -> buffer0 */ ioctl(fb_fd, FBIOPAN_DISPLAY, v); /* force-issue, even if cached 0 */ fb_active_yoffset = 0; logmsg("fb force-pan -> buffer0 (yoffset=0)"); } /* Restore the player's frame to its buffer and pan back to the original * yoffset. Called at menu/session end (BACK TO PLAYER / SIGTERM) so a frozen * player that never redraws shows its own frame again. Idempotent. */ void fb_restore_player(void) { int restore_y = fb_orig_yoffset; if (fb_fd >= 0 && restore_y >= 0 && fb_player_backup) { memcpy(fb_mem + (size_t)restore_y * fb_line, fb_player_backup, fb_player_backup_len); unsigned char v[160]; memset(v, 0, sizeof v); if (ioctl(fb_fd, FBIOGET_VSCREENINFO, v) == 0) { *(uint32_t *)(v + 16) = 0; *(uint32_t *)(v + 20) = (uint32_t)restore_y; if (ioctl(fb_fd, FBIOPAN_DISPLAY, v) < 0) logmsg("fb restore pan failed: %s", strerror(errno)); } logmsg("fb restore: player frame -> yoffset=%d (buffer %d)", restore_y, fb_yres ? restore_y / fb_yres : 0); } if (fb_player_backup) { free(fb_player_backup); fb_player_backup = NULL; } fb_orig_yoffset = -1; fb_player_backup_len = 0; } /* 把当前显示 buffer(buffer0)逐像素压暗(半透明遮罩模拟) */ void dim_screen(void) { for (int y = 0; y < fb_yres; y++) { unsigned char *row = fb + (size_t)y * fb_line; for (int x = 0; x < fb_xres; x++) { unsigned char *p = row + x * (fb_bpp / 8); if (fb_bpp == 16) { uint16_t c = *(uint16_t *)p; int r = (((c >> 11) & 0x1F) * 3) / 5; int g = (((c >> 5) & 0x3F) * 3) / 5; int b = ((c & 0x1F) * 3) / 5; *(uint16_t *)p = (uint16_t)((r << 11) | (g << 5) | b); } } } } /* A-0:电源菜单背景 = app 当前帧。从 fb0 buffer0(yoffset=0,app 永不 pan)读回 app * SIGSTOP 后的最后一帧 → back_buf,再经 dim_screen 压暗作菜单背景,替代旧的「压暗 * launcher 应用列表底」(Bug:应用内长按电源,背景错显示成应用列表)。 * 参考 fb_backup_player 的读回思路,但目标是 app 帧而非播放器帧(播放器帧在其自身 * yoffset,app 帧固定 buffer0)。MeowOS 主界面(无 app 运行)时 buffer0 = launcher * 列表帧,读回即现有「压暗列表底」效果,无回归。 */ void pm_capture_app_frame(void) { if (!fb_mem || !back_buf_ok || !fb) return; /* 无真实 fb / 直写模式:维持现状 */ memcpy(fb, fb_mem, (size_t)fb_line * (size_t)fb_yres); /* fb = SDK back_buf */ meow_dirty_full(); /* 新背景整帧呈现 */ logmsg("pm: menu bg = app frame (fb0 buffer0)"); }