checkpointing, heavy dev

This commit is contained in:
2025-07-24 14:32:50 +02:00
parent a3bc63d2d9
commit c2040a5c08
89 changed files with 741883 additions and 477 deletions

View File

@@ -1,3 +1,4 @@
// Package display provides framebuffer display functionality
package display
import (
@@ -18,10 +19,12 @@ type Display interface {
// FramebufferDisplay implements Display for Linux framebuffer
type FramebufferDisplay struct {
file *os.File
info *fbVarScreenInfo
memory []byte
device string
logger *slog.Logger
// Cached screen info
width uint32
height uint32
bpp uint32
}
type fbVarScreenInfo struct {
@@ -48,29 +51,29 @@ type fbBitfield struct {
const (
fbiogetVscreeninfo = 0x4600
bitsPerByte = 8
bpp32 = 32
bpp24 = 24
colorShift = 8
)
// NewFramebufferDisplay creates a new framebuffer display
func NewFramebufferDisplay(device string, logger *slog.Logger) (*FramebufferDisplay, error) {
file, err := os.OpenFile(device, os.O_RDWR, 0)
// Open framebuffer device temporarily to get screen info
file, err := os.OpenFile(device, os.O_RDWR, 0) // #nosec G304 - device path is controlled
if err != nil {
return nil, fmt.Errorf("opening framebuffer: %w", err)
}
defer func() { _ = file.Close() }()
var info fbVarScreenInfo
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, file.Fd(), fbiogetVscreeninfo, uintptr(unsafe.Pointer(&info)))
// #nosec G103 - required for framebuffer ioctl
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, file.Fd(), fbiogetVscreeninfo,
uintptr(unsafe.Pointer(&info)))
if errno != 0 {
_ = file.Close()
return nil, fmt.Errorf("getting screen info: %v", errno)
}
size := int(info.XRes * info.YRes * info.BitsPerPixel / 8)
memory, err := syscall.Mmap(int(file.Fd()), 0, size, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
if err != nil {
_ = file.Close()
return nil, fmt.Errorf("mapping framebuffer: %w", err)
}
logger.Info("framebuffer initialized",
"device", device,
"width", info.XRes,
@@ -78,45 +81,87 @@ func NewFramebufferDisplay(device string, logger *slog.Logger) (*FramebufferDisp
"bpp", info.BitsPerPixel)
return &FramebufferDisplay{
file: file,
info: &info,
memory: memory,
device: device,
logger: logger,
width: info.XRes,
height: info.YRes,
bpp: info.BitsPerPixel,
}, nil
}
// Show displays an image on the framebuffer
func (d *FramebufferDisplay) Show(img *image.RGBA) error {
if d == nil || d.device == "" {
return fmt.Errorf("invalid display")
}
// Open framebuffer device
file, err := os.OpenFile(d.device, os.O_RDWR, 0) // #nosec G304 - device path is controlled
if err != nil {
return fmt.Errorf("opening framebuffer: %w", err)
}
defer func() { _ = file.Close() }()
// Get screen information (in case it changed)
var info fbVarScreenInfo
// #nosec G103 - required for framebuffer ioctl
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, file.Fd(), fbiogetVscreeninfo,
uintptr(unsafe.Pointer(&info)))
if errno != 0 {
return fmt.Errorf("getting screen info: %v", errno)
}
bounds := img.Bounds()
width := bounds.Dx()
height := bounds.Dy()
if width > int(d.info.XRes) {
width = int(d.info.XRes)
if width > int(info.XRes) {
width = int(info.XRes)
}
if height > int(d.info.YRes) {
height = int(d.info.YRes)
if height > int(info.YRes) {
height = int(info.YRes)
}
// Create buffer for one line at a time
lineSize := int(info.XRes * info.BitsPerPixel / bitsPerByte)
line := make([]byte, lineSize)
// Write image data line by line
for y := 0; y < height; y++ {
// Clear line buffer
for i := range line {
line[i] = 0
}
// Fill line buffer with pixel data
for x := 0; x < width; x++ {
r, g, b, a := img.At(x, y).RGBA()
r, g, b = r>>8, g>>8, b>>8
r, g, b = r>>colorShift, g>>colorShift, b>>colorShift
offset := (y*int(d.info.XRes) + x) * int(d.info.BitsPerPixel/8)
if offset+3 < len(d.memory) {
if d.info.BitsPerPixel == 32 {
d.memory[offset] = byte(b)
d.memory[offset+1] = byte(g)
d.memory[offset+2] = byte(r)
d.memory[offset+3] = byte(a >> 8)
} else if d.info.BitsPerPixel == 24 {
d.memory[offset] = byte(b)
d.memory[offset+1] = byte(g)
d.memory[offset+2] = byte(r)
offset := x * int(info.BitsPerPixel/bitsPerByte)
if offset+3 < len(line) {
switch info.BitsPerPixel {
case bpp32:
line[offset] = byte(b)
line[offset+1] = byte(g)
line[offset+2] = byte(r)
line[offset+3] = byte(a >> colorShift)
case bpp24:
line[offset] = byte(b)
line[offset+1] = byte(g)
line[offset+2] = byte(r)
}
}
}
// Seek to correct position and write line
seekPos := int64(y) * int64(lineSize)
if _, err := file.Seek(seekPos, 0); err != nil {
return fmt.Errorf("seeking in framebuffer: %w", err)
}
if _, err := file.Write(line); err != nil {
return fmt.Errorf("writing to framebuffer: %w", err)
}
}
return nil
@@ -124,16 +169,56 @@ func (d *FramebufferDisplay) Show(img *image.RGBA) error {
// Clear clears the framebuffer
func (d *FramebufferDisplay) Clear() error {
for i := range d.memory {
d.memory[i] = 0
if d == nil || d.device == "" {
return fmt.Errorf("invalid display")
}
// Open framebuffer device
file, err := os.OpenFile(d.device, os.O_RDWR, 0) // #nosec G304 - device path is controlled
if err != nil {
return fmt.Errorf("opening framebuffer: %w", err)
}
defer func() { _ = file.Close() }()
// Get screen information
var info fbVarScreenInfo
// #nosec G103 - required for framebuffer ioctl
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, file.Fd(), fbiogetVscreeninfo,
uintptr(unsafe.Pointer(&info)))
if errno != 0 {
return fmt.Errorf("getting screen info: %v", errno)
}
// Create empty buffer
lineSize := int(info.XRes * info.BitsPerPixel / bitsPerByte)
emptyLine := make([]byte, lineSize)
// Write empty lines
for y := 0; y < int(info.YRes); y++ {
seekPos := int64(y) * int64(lineSize)
if _, err := file.Seek(seekPos, 0); err != nil {
return fmt.Errorf("seeking in framebuffer: %w", err)
}
if _, err := file.Write(emptyLine); err != nil {
return fmt.Errorf("writing to framebuffer: %w", err)
}
}
return nil
}
// Close closes the framebuffer
func (d *FramebufferDisplay) Close() error {
if err := syscall.Munmap(d.memory); err != nil {
d.logger.Error("unmapping framebuffer", "error", err)
}
return d.file.Close()
// Nothing to close since we open/close on each operation
return nil
}
// GetWidth returns the framebuffer width
func (d *FramebufferDisplay) GetWidth() uint32 {
return d.width
}
// GetHeight returns the framebuffer height
func (d *FramebufferDisplay) GetHeight() uint32 {
return d.height
}