mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-21 08:09:55 +00:00
120 lines
3.0 KiB
Go
120 lines
3.0 KiB
Go
//go:build !js && !ios && !android
|
|
|
|
package server
|
|
|
|
import (
|
|
"fmt"
|
|
"image"
|
|
"io"
|
|
)
|
|
|
|
// handleShowRemoteCursor handles the NetBird-specific RFB message used by
|
|
// the dashboard to toggle "show remote cursor" mode. Wire format: 1-byte
|
|
// enable flag (0/1) plus 6 padding bytes reserved for future arguments.
|
|
func (s *session) handleShowRemoteCursor() error {
|
|
var data [7]byte
|
|
if _, err := io.ReadFull(s.conn, data[:]); err != nil {
|
|
return fmt.Errorf("read showRemoteCursor: %w", err)
|
|
}
|
|
enable := data[0] != 0
|
|
s.encMu.Lock()
|
|
s.showRemoteCursor = enable
|
|
s.encMu.Unlock()
|
|
s.log.Debugf("show remote cursor: %v", enable)
|
|
return nil
|
|
}
|
|
|
|
// maybeCompositeCursor blends the current server cursor into img when the
|
|
// dashboard has enabled "show remote cursor" mode. Returns silently in
|
|
// every error path: a failed compositing must not stop the regular encode
|
|
// flow.
|
|
func (s *session) maybeCompositeCursor(img *image.RGBA) {
|
|
s.encMu.RLock()
|
|
enabled := s.showRemoteCursor
|
|
s.encMu.RUnlock()
|
|
if !enabled || img == nil {
|
|
return
|
|
}
|
|
src, ok := s.capturer.(cursorSource)
|
|
if !ok {
|
|
return
|
|
}
|
|
pos, ok := s.capturer.(cursorPositionSource)
|
|
if !ok {
|
|
return
|
|
}
|
|
cursorImg, hotX, hotY, _, err := src.Cursor()
|
|
if err != nil || cursorImg == nil {
|
|
s.cursorWarnOnce.Do(func() {
|
|
s.log.Warnf("remote cursor unavailable: %v", err)
|
|
})
|
|
return
|
|
}
|
|
posX, posY, err := pos.CursorPos()
|
|
if err != nil {
|
|
s.cursorWarnOnce.Do(func() {
|
|
s.log.Warnf("remote cursor position unavailable: %v", err)
|
|
})
|
|
return
|
|
}
|
|
compositeCursor(img, cursorImg, posX-hotX, posY-hotY)
|
|
}
|
|
|
|
// compositeCursor alpha-blends sprite onto frame at (dstX, dstY) using
|
|
// straight (non-premultiplied) alpha. Out-of-bounds destinations are
|
|
// clipped. Frames captured by our X11/Windows/macOS paths all advertise
|
|
// RGBA with a 255-only alpha channel, so the result keeps the framebuffer
|
|
// invariant ("opaque pixels everywhere") that the encoder depends on.
|
|
func compositeCursor(frame, sprite *image.RGBA, dstX, dstY int) {
|
|
fw, fh := frame.Rect.Dx(), frame.Rect.Dy()
|
|
sw, sh := sprite.Rect.Dx(), sprite.Rect.Dy()
|
|
if sw == 0 || sh == 0 {
|
|
return
|
|
}
|
|
|
|
x0, y0 := dstX, dstY
|
|
x1, y1 := dstX+sw, dstY+sh
|
|
if x0 < 0 {
|
|
x0 = 0
|
|
}
|
|
if y0 < 0 {
|
|
y0 = 0
|
|
}
|
|
if x1 > fw {
|
|
x1 = fw
|
|
}
|
|
if y1 > fh {
|
|
y1 = fh
|
|
}
|
|
if x0 >= x1 || y0 >= y1 {
|
|
return
|
|
}
|
|
|
|
fStride := frame.Stride
|
|
sStride := sprite.Stride
|
|
for y := y0; y < y1; y++ {
|
|
sy := y - dstY
|
|
fbRow := y * fStride
|
|
sRow := sy * sStride
|
|
for x := x0; x < x1; x++ {
|
|
sx := x - dstX
|
|
fbOff := fbRow + x*4
|
|
sOff := sRow + sx*4
|
|
a := uint32(sprite.Pix[sOff+3])
|
|
if a == 0 {
|
|
continue
|
|
}
|
|
if a == 255 {
|
|
frame.Pix[fbOff+0] = sprite.Pix[sOff+0]
|
|
frame.Pix[fbOff+1] = sprite.Pix[sOff+1]
|
|
frame.Pix[fbOff+2] = sprite.Pix[sOff+2]
|
|
continue
|
|
}
|
|
inv := 255 - a
|
|
frame.Pix[fbOff+0] = byte((uint32(sprite.Pix[sOff+0])*a + uint32(frame.Pix[fbOff+0])*inv) / 255)
|
|
frame.Pix[fbOff+1] = byte((uint32(sprite.Pix[sOff+1])*a + uint32(frame.Pix[fbOff+1])*inv) / 255)
|
|
frame.Pix[fbOff+2] = byte((uint32(sprite.Pix[sOff+2])*a + uint32(frame.Pix[fbOff+2])*inv) / 255)
|
|
}
|
|
}
|
|
}
|