checkpointing, heavy dev
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user