wraith/internal/rdp/pixelbuffer.go
Vantz Stockwell 8a096d7f7b
Some checks failed
Build & Sign Wraith / Build Windows + Sign (push) Has been cancelled
Wraith v0.1.0 — Desktop SSH + RDP + SFTP Client
Go + Wails v3 + Vue 3 + SQLite + FreeRDP3 (purego)
183 tests, 76 source files, 9,910 lines of code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 08:19:29 -04:00

104 lines
2.3 KiB
Go

package rdp
import "sync"
// PixelBuffer holds a shared RGBA pixel buffer that is updated by the RDP
// backend (via partial region updates) and read by the frame-serving path.
type PixelBuffer struct {
Width int
Height int
Data []byte // RGBA pixel data, len = Width * Height * 4
mu sync.RWMutex
dirty bool
}
// NewPixelBuffer allocates a buffer for the given resolution.
// The buffer is initialized to all zeros (transparent black).
func NewPixelBuffer(width, height int) *PixelBuffer {
return &PixelBuffer{
Width: width,
Height: height,
Data: make([]byte, width*height*4),
}
}
// Update applies a partial region update to the pixel buffer.
// The data slice must contain w*h*4 bytes of RGBA pixel data.
// Pixels outside the buffer bounds are silently clipped.
func (p *PixelBuffer) Update(x, y, w, h int, data []byte) {
p.mu.Lock()
defer p.mu.Unlock()
for row := 0; row < h; row++ {
destY := y + row
if destY < 0 || destY >= p.Height {
continue
}
srcOffset := row * w * 4
destOffset := (destY*p.Width + x) * 4
// Calculate how many pixels we can copy on this row
copyWidth := w
if x < 0 {
srcOffset += (-x) * 4
copyWidth += x // reduce by the clipped amount
destOffset = destY * p.Width * 4
}
if x+copyWidth > p.Width {
copyWidth = p.Width - x
if x < 0 {
copyWidth = p.Width
}
}
if copyWidth <= 0 {
continue
}
srcEnd := srcOffset + copyWidth*4
if srcEnd > len(data) {
srcEnd = len(data)
}
if srcOffset >= len(data) {
continue
}
destEnd := destOffset + copyWidth*4
if destEnd > len(p.Data) {
destEnd = len(p.Data)
}
copy(p.Data[destOffset:destEnd], data[srcOffset:srcEnd])
}
p.dirty = true
}
// GetFrame returns a copy of the full pixel buffer.
// The caller receives an independent copy that will not be affected
// by subsequent updates.
func (p *PixelBuffer) GetFrame() []byte {
p.mu.RLock()
defer p.mu.RUnlock()
frame := make([]byte, len(p.Data))
copy(frame, p.Data)
return frame
}
// IsDirty reports whether the buffer has been updated since the last
// ClearDirty call.
func (p *PixelBuffer) IsDirty() bool {
p.mu.RLock()
defer p.mu.RUnlock()
return p.dirty
}
// ClearDirty resets the dirty flag. Typically called after a frame has
// been sent to the frontend.
func (p *PixelBuffer) ClearDirty() {
p.mu.Lock()
defer p.mu.Unlock()
p.dirty = false
}