Files
cleonos/wine/cleonos_wine_lib/fb_window.py
2026-04-21 22:22:31 +08:00

144 lines
4.3 KiB
Python

from __future__ import annotations
import sys
import time
from typing import Optional
from .state import SharedKernelState
CLEONOS_KEY_LEFT = 0x01
CLEONOS_KEY_RIGHT = 0x02
CLEONOS_KEY_UP = 0x03
CLEONOS_KEY_DOWN = 0x04
class FBWindow:
def __init__(self, pygame_mod, width: int, height: int, scale: int, max_fps: int, *, verbose: bool = False) -> None:
self._pygame = pygame_mod
self.width = int(width)
self.height = int(height)
self.scale = max(1, int(scale))
self._verbose = bool(verbose)
self._closed = False
self._last_present_ns = 0
self._frame_interval_ns = int(1_000_000_000 / max_fps) if int(max_fps) > 0 else 0
window_w = self.width * self.scale
window_h = self.height * self.scale
self._pygame.display.set_caption("CLeonOS Wine Framebuffer")
self._screen = self._pygame.display.set_mode((window_w, window_h))
@classmethod
def create(
cls,
width: int,
height: int,
scale: int,
max_fps: int,
*,
verbose: bool = False,
) -> Optional["FBWindow"]:
try:
import pygame # type: ignore
except Exception as exc:
print(f"[WINE][WARN] framebuffer window disabled: pygame import failed ({exc})", file=sys.stderr)
return None
try:
pygame.init()
pygame.display.init()
return cls(pygame, width, height, scale, max_fps, verbose=verbose)
except Exception as exc:
print(f"[WINE][WARN] framebuffer window disabled: unable to init display ({exc})", file=sys.stderr)
try:
pygame.quit()
except Exception:
pass
return None
def close(self) -> None:
if self._closed:
return
self._closed = True
try:
self._pygame.display.quit()
except Exception:
pass
try:
self._pygame.quit()
except Exception:
pass
def is_closed(self) -> bool:
return self._closed
def pump_input(self, state: SharedKernelState) -> None:
if self._closed:
return
try:
events = self._pygame.event.get()
except Exception:
self.close()
return
for event in events:
if event.type == self._pygame.QUIT:
self.close()
continue
if event.type != self._pygame.KEYDOWN:
continue
key = event.key
ch = 0
if key == self._pygame.K_LEFT:
ch = CLEONOS_KEY_LEFT
elif key == self._pygame.K_RIGHT:
ch = CLEONOS_KEY_RIGHT
elif key == self._pygame.K_UP:
ch = CLEONOS_KEY_UP
elif key == self._pygame.K_DOWN:
ch = CLEONOS_KEY_DOWN
elif key in (self._pygame.K_RETURN, self._pygame.K_KP_ENTER):
ch = ord("\n")
elif key == self._pygame.K_BACKSPACE:
ch = 8
elif key == self._pygame.K_ESCAPE:
ch = 27
elif key == self._pygame.K_TAB:
ch = ord("\t")
else:
text = getattr(event, "unicode", "")
if isinstance(text, str) and len(text) == 1:
code = ord(text)
if 32 <= code <= 126:
ch = code
if ch != 0:
state.push_key(ch)
def present(self, pixels_bgra: bytearray, *, force: bool = False) -> bool:
if self._closed:
return False
now_ns = time.monotonic_ns()
if not force and self._frame_interval_ns > 0 and (now_ns - self._last_present_ns) < self._frame_interval_ns:
return False
try:
src = self._pygame.image.frombuffer(pixels_bgra, (self.width, self.height), "BGRA")
if self.scale != 1:
src = self._pygame.transform.scale(src, (self.width * self.scale, self.height * self.scale))
self._screen.blit(src, (0, 0))
self._pygame.display.flip()
self._last_present_ns = now_ns
return True
except Exception:
self.close()
return False