主要变更: - RDP 远程桌面:新增 backend/pkg/rdp(RDP 引擎 + credssp/display/rle/trace)、 backend/controller/rdp*(连接/凭证/文件/会话/实时)、frontend RDP 组件与 API - Windows SMB 文件传输:backend/pkg/winsmb - 云账单同步:backend/pkg/billingsync + billing_sync/aliyun_billing,前端账单详情 - Agent CLI Kubernetes 工具:kubernetes_action/get、pod_execute/logs - 服务器导入助手:server_import_helpers、ImportCredentialSettings - 国际化与组件重构(cloud/deploy/monitor/server/system 等多模块) - 清理编译产物二进制:移除 backend/server、backend/agent/bin/aiops-agent-* - .gitignore:新增 backend/agent/ 与 .cache/(避免 334MB go-build 缓存误入库) Co-Authored-By: Claude <noreply@anthropic.com>
146 lines
3.5 KiB
Go
146 lines
3.5 KiB
Go
package display
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"encoding/binary"
|
|
"image"
|
|
"image/draw"
|
|
"image/png"
|
|
)
|
|
|
|
// Bitmap 表示协议层解码后的 BGRA/RGB565 小块,display 只负责合成和输出 // Logan.Li
|
|
type Bitmap struct {
|
|
X int
|
|
Y int
|
|
Width int
|
|
Height int
|
|
BitsPerPixel int
|
|
Data []byte
|
|
}
|
|
|
|
// Frame 表示一次合成后的脏区域输出 // Logan.Li
|
|
type Frame struct {
|
|
X int
|
|
Y int
|
|
Width int
|
|
Height int
|
|
Image string
|
|
RGBA []byte
|
|
}
|
|
|
|
// Framebuffer 借鉴 Guacamole/bring 的 display/layer 思路,在后端维护完整画布 // Logan.Li
|
|
type Framebuffer struct {
|
|
width int
|
|
height int
|
|
img *image.RGBA
|
|
dirty image.Rectangle
|
|
}
|
|
|
|
// NewFramebuffer 创建固定尺寸远端画布,所有 RDP 更新先合成到这里 // Logan.Li
|
|
func NewFramebuffer(width, height int) *Framebuffer {
|
|
if width <= 0 {
|
|
width = 1280
|
|
}
|
|
if height <= 0 {
|
|
height = 800
|
|
}
|
|
return &Framebuffer{
|
|
width: width,
|
|
height: height,
|
|
img: image.NewRGBA(image.Rect(0, 0, width, height)),
|
|
}
|
|
}
|
|
|
|
// Draw 合成一个 bitmap 小块,并扩大脏矩形 // Logan.Li
|
|
func (f *Framebuffer) Draw(b Bitmap) {
|
|
if b.Width <= 0 || b.Height <= 0 || len(b.Data) == 0 {
|
|
return
|
|
}
|
|
dstRect := image.Rect(b.X, b.Y, b.X+b.Width, b.Y+b.Height).Intersect(f.img.Bounds())
|
|
if dstRect.Empty() {
|
|
return
|
|
}
|
|
src := bitmapToRGBA(b)
|
|
srcPoint := image.Pt(dstRect.Min.X-b.X, dstRect.Min.Y-b.Y)
|
|
draw.Draw(f.img, dstRect, src, srcPoint, draw.Src)
|
|
if f.dirty.Empty() {
|
|
f.dirty = dstRect
|
|
} else {
|
|
f.dirty = f.dirty.Union(dstRect)
|
|
}
|
|
}
|
|
|
|
// Flush 输出当前脏区域 PNG,并清空脏矩形 // Logan.Li
|
|
func (f *Framebuffer) Flush() (Frame, bool) {
|
|
frame, ok := f.FlushRGBA()
|
|
if !ok {
|
|
return Frame{}, false
|
|
}
|
|
img := image.NewRGBA(image.Rect(0, 0, frame.Width, frame.Height))
|
|
copy(img.Pix, frame.RGBA)
|
|
var buf bytes.Buffer
|
|
if err := png.Encode(&buf, img); err != nil {
|
|
return Frame{}, false
|
|
}
|
|
frame.Image = "data:image/png;base64," + base64.StdEncoding.EncodeToString(buf.Bytes())
|
|
return frame, true
|
|
}
|
|
|
|
// FlushRGBA 输出当前脏区域原始 RGBA,避免实时 RDP 路径重复 PNG/base64 编解码。 // Logan.Li
|
|
func (f *Framebuffer) FlushRGBA() (Frame, bool) {
|
|
if f.dirty.Empty() {
|
|
return Frame{}, false
|
|
}
|
|
rect := f.dirty.Intersect(f.img.Bounds())
|
|
f.dirty = image.Rectangle{}
|
|
if rect.Empty() {
|
|
return Frame{}, false
|
|
}
|
|
crop := image.NewRGBA(image.Rect(0, 0, rect.Dx(), rect.Dy()))
|
|
draw.Draw(crop, crop.Bounds(), f.img, rect.Min, draw.Src)
|
|
return Frame{
|
|
X: rect.Min.X,
|
|
Y: rect.Min.Y,
|
|
Width: rect.Dx(),
|
|
Height: rect.Dy(),
|
|
RGBA: append([]byte(nil), crop.Pix...),
|
|
}, true
|
|
}
|
|
|
|
func bitmapToRGBA(b Bitmap) *image.RGBA {
|
|
img := image.NewRGBA(image.Rect(0, 0, b.Width, b.Height))
|
|
pix := img.Pix
|
|
switch b.BitsPerPixel {
|
|
case 1, 2:
|
|
n := b.Width * b.Height
|
|
for i := 0; i < n && i*2+1 < len(b.Data); i++ {
|
|
// grdp 的 RLE 输出 16bpp 使用大端 RGB565,按小端会导致蓝紫/绿色偏色。 // Logan.Li
|
|
c := binary.BigEndian.Uint16(b.Data[i*2 : i*2+2])
|
|
r := uint8((c >> 11) & 0x1F)
|
|
g := uint8((c >> 5) & 0x3F)
|
|
bl := uint8(c & 0x1F)
|
|
off := i * 4
|
|
pix[off] = (r << 3) | (r >> 2)
|
|
pix[off+1] = (g << 2) | (g >> 4)
|
|
pix[off+2] = (bl << 3) | (bl >> 2)
|
|
pix[off+3] = 0xFF
|
|
}
|
|
default:
|
|
stride := b.BitsPerPixel
|
|
n := b.Width * b.Height
|
|
for i := 0; i < n; i++ {
|
|
s := i * stride
|
|
if s+2 >= len(b.Data) {
|
|
break
|
|
}
|
|
off := i * 4
|
|
pix[off] = b.Data[s+2]
|
|
pix[off+1] = b.Data[s+1]
|
|
pix[off+2] = b.Data[s]
|
|
pix[off+3] = 0xFF
|
|
}
|
|
}
|
|
return img
|
|
}
|