mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-31 21:19:55 +00: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 {
|
||||
mu sync.Mutex
|
||||
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
|
||||
// most recent successful GetCursorImage. cursorPositionSource readers
|
||||
// share this value so we do not pay a second X round-trip per frame.
|
||||
lastPosX, lastPosY int
|
||||
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
|
||||
@@ -42,24 +47,31 @@ func newXFixesCursor(conn *xgb.Conn) (*xfixesCursor, error) {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.runtimeErr != nil {
|
||||
return nil, 0, 0, 0, c.runtimeErr
|
||||
}
|
||||
reply, err := xfixes.GetCursorImage(c.conn).Reply()
|
||||
if err != nil {
|
||||
c.runtimeErr = fmt.Errorf("xfixes GetCursorImage: %w", err)
|
||||
return nil, 0, 0, 0, c.runtimeErr
|
||||
if c.lastImg != nil {
|
||||
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
|
||||
w, h := int(reply.Width), int(reply.Height)
|
||||
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")
|
||||
}
|
||||
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)
|
||||
}
|
||||
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+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
|
||||
|
||||
@@ -604,10 +604,6 @@ func (s *Server) handleConnection(conn net.Conn) {
|
||||
serverW: capturer.Width(),
|
||||
serverH: capturer.Height(),
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -99,10 +99,6 @@ type session struct {
|
||||
// source so the encoder stops polling for the rest of the session.
|
||||
// Reset on SetEncodings so a reconnect can retry.
|
||||
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
|
||||
// cursor sprite into the captured framebuffer at the remote position
|
||||
// instead of emitting the Cursor pseudo-encoding. Toggled by the
|
||||
@@ -462,9 +458,6 @@ func (s *session) applyEncoding(enc int32) string {
|
||||
s.clientSupportsExtClipboard = true
|
||||
return "ext-clipboard"
|
||||
case pseudoEncCursor:
|
||||
if s.disableCursor {
|
||||
return ""
|
||||
}
|
||||
s.clientSupportsCursor = true
|
||||
return "cursor"
|
||||
case pseudoEncExtendedMouseButtons:
|
||||
|
||||
Reference in New Issue
Block a user