Major changes: - Converted all cobra commands from global variables to CLI struct methods - Eliminated global logger variable in favor of dependency injection - Fixed all errcheck linter issues by properly handling errors - Fixed Makefile to check formatting instead of modifying files - Integrated smartconfig library for configuration management - Added CLAUDE.md with project-specific development guidelines Key improvements: - All commands (daemon, install, status, info) now use CLI struct methods - Logger is injected as dependency through fx providers - Proper error handling for all DrawText and file.Close() calls - Configuration loading now uses smartconfig with proper defaults - Fixed formatting check in Makefile (make test no longer modifies files) Technical details: - Created CLI struct with log field (renamed from logger per request) - All command constructors return *cobra.Command from CLI methods - Config package uses smartconfig.NewFromAppName() correctly - Fixed all critical errcheck issues throughout the codebase - Maintained backward compatibility with existing functionality All tests passing, code formatted, and ready for deployment.
140 lines
3.0 KiB
Go
140 lines
3.0 KiB
Go
package display
|
|
|
|
import (
|
|
"fmt"
|
|
"image"
|
|
"log/slog"
|
|
"os"
|
|
"syscall"
|
|
"unsafe"
|
|
)
|
|
|
|
// Display interface for showing images
|
|
type Display interface {
|
|
Show(img *image.RGBA) error
|
|
Clear() error
|
|
Close() error
|
|
}
|
|
|
|
// FramebufferDisplay implements Display for Linux framebuffer
|
|
type FramebufferDisplay struct {
|
|
file *os.File
|
|
info *fbVarScreenInfo
|
|
memory []byte
|
|
logger *slog.Logger
|
|
}
|
|
|
|
type fbVarScreenInfo struct {
|
|
XRes uint32
|
|
YRes uint32
|
|
XResVirtual uint32
|
|
YResVirtual uint32
|
|
XOffset uint32
|
|
YOffset uint32
|
|
BitsPerPixel uint32
|
|
Grayscale uint32
|
|
Red fbBitfield
|
|
Green fbBitfield
|
|
Blue fbBitfield
|
|
Transp fbBitfield
|
|
_ [4]byte
|
|
}
|
|
|
|
type fbBitfield struct {
|
|
Offset uint32
|
|
Length uint32
|
|
Right uint32
|
|
}
|
|
|
|
const (
|
|
fbiogetVscreeninfo = 0x4600
|
|
)
|
|
|
|
// NewFramebufferDisplay creates a new framebuffer display
|
|
func NewFramebufferDisplay(device string, logger *slog.Logger) (*FramebufferDisplay, error) {
|
|
file, err := os.OpenFile(device, os.O_RDWR, 0)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("opening framebuffer: %w", err)
|
|
}
|
|
|
|
var info fbVarScreenInfo
|
|
_, _, 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,
|
|
"height", info.YRes,
|
|
"bpp", info.BitsPerPixel)
|
|
|
|
return &FramebufferDisplay{
|
|
file: file,
|
|
info: &info,
|
|
memory: memory,
|
|
logger: logger,
|
|
}, nil
|
|
}
|
|
|
|
// Show displays an image on the framebuffer
|
|
func (d *FramebufferDisplay) Show(img *image.RGBA) error {
|
|
bounds := img.Bounds()
|
|
width := bounds.Dx()
|
|
height := bounds.Dy()
|
|
|
|
if width > int(d.info.XRes) {
|
|
width = int(d.info.XRes)
|
|
}
|
|
if height > int(d.info.YRes) {
|
|
height = int(d.info.YRes)
|
|
}
|
|
|
|
for y := 0; y < height; y++ {
|
|
for x := 0; x < width; x++ {
|
|
r, g, b, a := img.At(x, y).RGBA()
|
|
r, g, b = r>>8, g>>8, b>>8
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Clear clears the framebuffer
|
|
func (d *FramebufferDisplay) Clear() error {
|
|
for i := range d.memory {
|
|
d.memory[i] = 0
|
|
}
|
|
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()
|
|
}
|