Define the RDPBackend interface, RDPConfig, and FrameUpdate types that abstract FreeRDP behind a pluggable backend. Add PixelBuffer for shared RGBA frame management with partial-update support and dirty tracking. Implement full 104-key US keyboard scancode map (JS KeyboardEvent.code to RDP hardware scancodes) with extended-key detection helpers and mouse event flag constants matching MS-RDPBCGR. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
173 lines
4.3 KiB
Go
173 lines
4.3 KiB
Go
package rdp
|
|
|
|
import "testing"
|
|
|
|
func TestNewPixelBuffer(t *testing.T) {
|
|
pb := NewPixelBuffer(100, 50)
|
|
|
|
if pb.Width != 100 {
|
|
t.Errorf("Width = %d, want 100", pb.Width)
|
|
}
|
|
if pb.Height != 50 {
|
|
t.Errorf("Height = %d, want 50", pb.Height)
|
|
}
|
|
|
|
expectedLen := 100 * 50 * 4
|
|
if len(pb.Data) != expectedLen {
|
|
t.Errorf("Data length = %d, want %d", len(pb.Data), expectedLen)
|
|
}
|
|
|
|
// All pixels should be initialized to zero
|
|
for i, b := range pb.Data {
|
|
if b != 0 {
|
|
t.Errorf("Data[%d] = %d, want 0", i, b)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNewPixelBufferSmall(t *testing.T) {
|
|
pb := NewPixelBuffer(1, 1)
|
|
if len(pb.Data) != 4 {
|
|
t.Errorf("1x1 buffer Data length = %d, want 4", len(pb.Data))
|
|
}
|
|
}
|
|
|
|
func TestUpdateRegion(t *testing.T) {
|
|
pb := NewPixelBuffer(10, 10) // 10x10 pixels
|
|
|
|
// Create a 3x2 red patch
|
|
patch := make([]byte, 3*2*4) // 3 wide, 2 tall
|
|
for i := 0; i < 3*2; i++ {
|
|
patch[i*4+0] = 255 // R
|
|
patch[i*4+1] = 0 // G
|
|
patch[i*4+2] = 0 // B
|
|
patch[i*4+3] = 255 // A
|
|
}
|
|
|
|
// Apply at position (2, 3)
|
|
pb.Update(2, 3, 3, 2, patch)
|
|
|
|
// Check that pixel (2,3) is red
|
|
offset := (3*10 + 2) * 4
|
|
if pb.Data[offset+0] != 255 || pb.Data[offset+1] != 0 || pb.Data[offset+2] != 0 || pb.Data[offset+3] != 255 {
|
|
t.Errorf("pixel (2,3) = [%d,%d,%d,%d], want [255,0,0,255]",
|
|
pb.Data[offset+0], pb.Data[offset+1], pb.Data[offset+2], pb.Data[offset+3])
|
|
}
|
|
|
|
// Check that pixel (4,4) is red (last pixel of patch)
|
|
offset = (4*10 + 4) * 4
|
|
if pb.Data[offset+0] != 255 || pb.Data[offset+1] != 0 || pb.Data[offset+2] != 0 || pb.Data[offset+3] != 255 {
|
|
t.Errorf("pixel (4,4) = [%d,%d,%d,%d], want [255,0,0,255]",
|
|
pb.Data[offset+0], pb.Data[offset+1], pb.Data[offset+2], pb.Data[offset+3])
|
|
}
|
|
|
|
// Check that pixel (1,3) is still black (just outside patch)
|
|
offset = (3*10 + 1) * 4
|
|
if pb.Data[offset+0] != 0 || pb.Data[offset+3] != 0 {
|
|
t.Errorf("pixel (1,3) should be untouched, got [%d,%d,%d,%d]",
|
|
pb.Data[offset+0], pb.Data[offset+1], pb.Data[offset+2], pb.Data[offset+3])
|
|
}
|
|
|
|
// Check that pixel (5,3) is still black (just outside patch)
|
|
offset = (3*10 + 5) * 4
|
|
if pb.Data[offset+0] != 0 || pb.Data[offset+3] != 0 {
|
|
t.Errorf("pixel (5,3) should be untouched, got [%d,%d,%d,%d]",
|
|
pb.Data[offset+0], pb.Data[offset+1], pb.Data[offset+2], pb.Data[offset+3])
|
|
}
|
|
}
|
|
|
|
func TestUpdateRegionClipping(t *testing.T) {
|
|
pb := NewPixelBuffer(10, 10)
|
|
|
|
// Create a 5x5 green patch and place it at (8, 8), so it overflows
|
|
patch := make([]byte, 5*5*4)
|
|
for i := 0; i < 5*5; i++ {
|
|
patch[i*4+0] = 0
|
|
patch[i*4+1] = 255
|
|
patch[i*4+2] = 0
|
|
patch[i*4+3] = 255
|
|
}
|
|
|
|
// Should not panic — overflowing regions are clipped
|
|
pb.Update(8, 8, 5, 5, patch)
|
|
|
|
// Pixel (8,8) should be green
|
|
offset := (8*10 + 8) * 4
|
|
if pb.Data[offset+1] != 255 {
|
|
t.Errorf("pixel (8,8) G = %d, want 255", pb.Data[offset+1])
|
|
}
|
|
|
|
// Pixel (9,9) should also be green (last valid pixel)
|
|
offset = (9*10 + 9) * 4
|
|
if pb.Data[offset+1] != 255 {
|
|
t.Errorf("pixel (9,9) G = %d, want 255", pb.Data[offset+1])
|
|
}
|
|
}
|
|
|
|
func TestDirtyFlag(t *testing.T) {
|
|
pb := NewPixelBuffer(10, 10)
|
|
|
|
if pb.IsDirty() {
|
|
t.Error("new buffer should not be dirty")
|
|
}
|
|
|
|
// Update a region
|
|
patch := make([]byte, 4) // 1x1 pixel
|
|
patch[0] = 255
|
|
patch[3] = 255
|
|
pb.Update(0, 0, 1, 1, patch)
|
|
|
|
if !pb.IsDirty() {
|
|
t.Error("buffer should be dirty after update")
|
|
}
|
|
|
|
pb.ClearDirty()
|
|
|
|
if pb.IsDirty() {
|
|
t.Error("buffer should not be dirty after ClearDirty")
|
|
}
|
|
}
|
|
|
|
func TestGetFrameReturnsCopy(t *testing.T) {
|
|
pb := NewPixelBuffer(2, 2)
|
|
|
|
// Set first pixel to white
|
|
patch := []byte{255, 255, 255, 255}
|
|
pb.Update(0, 0, 1, 1, patch)
|
|
|
|
frame1 := pb.GetFrame()
|
|
|
|
// Modify the returned frame
|
|
frame1[0] = 0
|
|
|
|
// Get another frame — it should still have the original value
|
|
frame2 := pb.GetFrame()
|
|
if frame2[0] != 255 {
|
|
t.Errorf("GetFrame did not return an independent copy: got %d, want 255", frame2[0])
|
|
}
|
|
}
|
|
|
|
func TestFullFrameUpdate(t *testing.T) {
|
|
pb := NewPixelBuffer(4, 4)
|
|
|
|
// Create a full-frame update with all blue pixels
|
|
fullFrame := make([]byte, 4*4*4)
|
|
for i := 0; i < 4*4; i++ {
|
|
fullFrame[i*4+0] = 0
|
|
fullFrame[i*4+1] = 0
|
|
fullFrame[i*4+2] = 255
|
|
fullFrame[i*4+3] = 255
|
|
}
|
|
|
|
pb.Update(0, 0, 4, 4, fullFrame)
|
|
|
|
frame := pb.GetFrame()
|
|
for i := 0; i < 4*4; i++ {
|
|
if frame[i*4+2] != 255 {
|
|
t.Errorf("pixel %d blue channel = %d, want 255", i, frame[i*4+2])
|
|
break
|
|
}
|
|
}
|
|
}
|