mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-20 20:39:08 +02:00
Allow Cursor pseudo-encoding in session mode and cache last XFixes sprite
This commit is contained in:
@@ -17,15 +17,20 @@ import (
|
|||||||
type xfixesCursor struct {
|
type xfixesCursor struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
conn *xgb.Conn
|
conn *xgb.Conn
|
||||||
// runtimeErr latches the first GetCursorImage failure so subsequent
|
|
||||||
// calls return quickly without another X round-trip. Some virtual
|
|
||||||
// displays advertise XFixes but reject GetCursorImage (Xvfb).
|
|
||||||
runtimeErr error
|
|
||||||
// lastPosX/lastPosY hold the cursor screen position observed on the
|
// lastPosX/lastPosY hold the cursor screen position observed on the
|
||||||
// most recent successful GetCursorImage. cursorPositionSource readers
|
// most recent successful GetCursorImage. cursorPositionSource readers
|
||||||
// share this value so we do not pay a second X round-trip per frame.
|
// share this value so we do not pay a second X round-trip per frame.
|
||||||
lastPosX, lastPosY int
|
lastPosX, lastPosY int
|
||||||
hasPos bool
|
hasPos bool
|
||||||
|
// lastImg, lastHotX, lastHotY, lastSerial cache the most recent good
|
||||||
|
// GetCursorImage result so transient failures (cursor hidden, server
|
||||||
|
// briefly unresponsive) reuse the previous sprite instead of going
|
||||||
|
// dark. Without this the encoder's compositing path drops to no-op as
|
||||||
|
// soon as the cursor becomes momentarily unavailable.
|
||||||
|
lastImg *image.RGBA
|
||||||
|
lastHotX int
|
||||||
|
lastHotY int
|
||||||
|
lastSerial uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// newXFixesCursor initialises the XFixes extension on conn. Returns an
|
// newXFixesCursor initialises the XFixes extension on conn. Returns an
|
||||||
@@ -42,24 +47,31 @@ func newXFixesCursor(conn *xgb.Conn) (*xfixesCursor, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cursor returns the current cursor sprite as RGBA along with its hotspot
|
// Cursor returns the current cursor sprite as RGBA along with its hotspot
|
||||||
// and serial. Callers should treat an unchanged serial as "no update".
|
// and serial. Callers should treat an unchanged serial as "no update". On
|
||||||
|
// a transient GetCursorImage failure the last cached sprite is returned
|
||||||
|
// so compositing keeps painting the cursor instead of disappearing.
|
||||||
func (c *xfixesCursor) Cursor() (*image.RGBA, int, int, uint64, error) {
|
func (c *xfixesCursor) Cursor() (*image.RGBA, int, int, uint64, error) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
if c.runtimeErr != nil {
|
|
||||||
return nil, 0, 0, 0, c.runtimeErr
|
|
||||||
}
|
|
||||||
reply, err := xfixes.GetCursorImage(c.conn).Reply()
|
reply, err := xfixes.GetCursorImage(c.conn).Reply()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.runtimeErr = fmt.Errorf("xfixes GetCursorImage: %w", err)
|
if c.lastImg != nil {
|
||||||
return nil, 0, 0, 0, c.runtimeErr
|
return c.lastImg, c.lastHotX, c.lastHotY, c.lastSerial, nil
|
||||||
|
}
|
||||||
|
return nil, 0, 0, 0, fmt.Errorf("xfixes GetCursorImage: %w", err)
|
||||||
}
|
}
|
||||||
c.lastPosX, c.lastPosY, c.hasPos = int(reply.X), int(reply.Y), true
|
c.lastPosX, c.lastPosY, c.hasPos = int(reply.X), int(reply.Y), true
|
||||||
w, h := int(reply.Width), int(reply.Height)
|
w, h := int(reply.Width), int(reply.Height)
|
||||||
if w <= 0 || h <= 0 {
|
if w <= 0 || h <= 0 {
|
||||||
|
if c.lastImg != nil {
|
||||||
|
return c.lastImg, c.lastHotX, c.lastHotY, c.lastSerial, nil
|
||||||
|
}
|
||||||
return nil, 0, 0, 0, fmt.Errorf("cursor has zero extent")
|
return nil, 0, 0, 0, fmt.Errorf("cursor has zero extent")
|
||||||
}
|
}
|
||||||
if len(reply.CursorImage) < w*h {
|
if len(reply.CursorImage) < w*h {
|
||||||
|
if c.lastImg != nil {
|
||||||
|
return c.lastImg, c.lastHotX, c.lastHotY, c.lastSerial, nil
|
||||||
|
}
|
||||||
return nil, 0, 0, 0, fmt.Errorf("cursor pixel buffer truncated: %d < %d", len(reply.CursorImage), w*h)
|
return nil, 0, 0, 0, fmt.Errorf("cursor pixel buffer truncated: %d < %d", len(reply.CursorImage), w*h)
|
||||||
}
|
}
|
||||||
img := image.NewRGBA(image.Rect(0, 0, w, h))
|
img := image.NewRGBA(image.Rect(0, 0, w, h))
|
||||||
@@ -72,7 +84,11 @@ func (c *xfixesCursor) Cursor() (*image.RGBA, int, int, uint64, error) {
|
|||||||
img.Pix[o+2] = byte(p)
|
img.Pix[o+2] = byte(p)
|
||||||
img.Pix[o+3] = byte(p >> 24)
|
img.Pix[o+3] = byte(p >> 24)
|
||||||
}
|
}
|
||||||
return img, int(reply.Xhot), int(reply.Yhot), uint64(reply.CursorSerial), nil
|
c.lastImg = img
|
||||||
|
c.lastHotX = int(reply.Xhot)
|
||||||
|
c.lastHotY = int(reply.Yhot)
|
||||||
|
c.lastSerial = uint64(reply.CursorSerial)
|
||||||
|
return img, c.lastHotX, c.lastHotY, c.lastSerial, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cursor on X11Capturer satisfies cursorSource. The XFixes binding is
|
// Cursor on X11Capturer satisfies cursorSource. The XFixes binding is
|
||||||
|
|||||||
@@ -604,10 +604,6 @@ func (s *Server) handleConnection(conn net.Conn) {
|
|||||||
serverW: capturer.Width(),
|
serverW: capturer.Width(),
|
||||||
serverH: capturer.Height(),
|
serverH: capturer.Height(),
|
||||||
log: connLog,
|
log: connLog,
|
||||||
// Virtual sessions run on Xvfb which has no usable cursor source,
|
|
||||||
// so we skip the Cursor pseudo-encoding and let the client's
|
|
||||||
// local fallback show instead.
|
|
||||||
disableCursor: header.mode == ModeSession,
|
|
||||||
}
|
}
|
||||||
sess.serve()
|
sess.serve()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,10 +99,6 @@ type session struct {
|
|||||||
// source so the encoder stops polling for the rest of the session.
|
// source so the encoder stops polling for the rest of the session.
|
||||||
// Reset on SetEncodings so a reconnect can retry.
|
// Reset on SetEncodings so a reconnect can retry.
|
||||||
cursorSourceFailed bool
|
cursorSourceFailed bool
|
||||||
// disableCursor suppresses the Cursor pseudo-encoding regardless of
|
|
||||||
// what the client advertises. Set for virtual sessions where no
|
|
||||||
// usable cursor source exists. Constant for the session lifetime.
|
|
||||||
disableCursor bool
|
|
||||||
// showRemoteCursor switches the encoder to compositing the server
|
// showRemoteCursor switches the encoder to compositing the server
|
||||||
// cursor sprite into the captured framebuffer at the remote position
|
// cursor sprite into the captured framebuffer at the remote position
|
||||||
// instead of emitting the Cursor pseudo-encoding. Toggled by the
|
// instead of emitting the Cursor pseudo-encoding. Toggled by the
|
||||||
@@ -462,9 +458,6 @@ func (s *session) applyEncoding(enc int32) string {
|
|||||||
s.clientSupportsExtClipboard = true
|
s.clientSupportsExtClipboard = true
|
||||||
return "ext-clipboard"
|
return "ext-clipboard"
|
||||||
case pseudoEncCursor:
|
case pseudoEncCursor:
|
||||||
if s.disableCursor {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
s.clientSupportsCursor = true
|
s.clientSupportsCursor = true
|
||||||
return "cursor"
|
return "cursor"
|
||||||
case pseudoEncExtendedMouseButtons:
|
case pseudoEncExtendedMouseButtons:
|
||||||
|
|||||||
Reference in New Issue
Block a user