mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-23 07:09:08 +02:00
Retry a transient cursor-source failure and stop reporting stale framebuffer geometry
This commit is contained in:
@@ -53,22 +53,33 @@ func (p *FBPoller) ClientDisconnect() {
|
|||||||
if p.clients <= 0 && p.capturer != nil {
|
if p.clients <= 0 && p.capturer != nil {
|
||||||
p.capturer.Close()
|
p.capturer.Close()
|
||||||
p.capturer = nil
|
p.capturer = nil
|
||||||
|
// Drop the geometry with the device. The next client re-reads it, and
|
||||||
|
// the framebuffer may be gone or resized by then.
|
||||||
|
p.w, p.h = 0, 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Width returns the framebuffer width, doing lazy init if needed.
|
// Width returns the framebuffer width, doing lazy init if needed. Zero when
|
||||||
|
// the device cannot be opened: the geometry cached from an earlier session is
|
||||||
|
// no evidence the framebuffer is still there, and reporting it admits a VNC
|
||||||
|
// session whose every frame then fails.
|
||||||
func (p *FBPoller) Width() int {
|
func (p *FBPoller) Width() int {
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
defer p.mu.Unlock()
|
defer p.mu.Unlock()
|
||||||
_ = p.ensureCapturerLocked()
|
if err := p.ensureCapturerLocked(); err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
return p.w
|
return p.w
|
||||||
}
|
}
|
||||||
|
|
||||||
// Height returns the framebuffer height, doing lazy init if needed.
|
// Height returns the framebuffer height, doing lazy init if needed. Zero when
|
||||||
|
// the device cannot be opened, for the reason given on Width.
|
||||||
func (p *FBPoller) Height() int {
|
func (p *FBPoller) Height() int {
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
defer p.mu.Unlock()
|
defer p.mu.Unlock()
|
||||||
_ = p.ensureCapturerLocked()
|
if err := p.ensureCapturerLocked(); err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
return p.h
|
return p.h
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,6 +114,7 @@ func (p *FBPoller) Close() {
|
|||||||
p.capturer.Close()
|
p.capturer.Close()
|
||||||
p.capturer = nil
|
p.capturer = nil
|
||||||
}
|
}
|
||||||
|
p.w, p.h = 0, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *FBPoller) ensureCapturerLocked() error {
|
func (p *FBPoller) ensureCapturerLocked() error {
|
||||||
|
|||||||
@@ -114,6 +114,12 @@ 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
|
||||||
|
// cursorSourceFailures counts consecutive errors from the cursor source.
|
||||||
|
// A single error does not latch: the Windows sampler fails across a
|
||||||
|
// desktop switch and recovers on the next capture, and latching there
|
||||||
|
// costs the client its cursor for the rest of the session. Reset by the
|
||||||
|
// first success.
|
||||||
|
cursorSourceFailures int
|
||||||
// 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
|
||||||
@@ -489,6 +495,7 @@ func (s *session) resetEncodingCaps() {
|
|||||||
s.clientSupportsCursor = false
|
s.clientSupportsCursor = false
|
||||||
s.clientSupportsExtMouseButtons = false
|
s.clientSupportsExtMouseButtons = false
|
||||||
s.cursorSourceFailed = false
|
s.cursorSourceFailed = false
|
||||||
|
s.cursorSourceFailures = 0
|
||||||
s.clientJPEGQuality = -1
|
s.clientJPEGQuality = -1
|
||||||
s.clientZlibLevel = -1
|
s.clientZlibLevel = -1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,13 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// cursorSourceFailureLimit is how many consecutive Cursor() errors are taken as
|
||||||
|
// the source being genuinely unavailable rather than momentarily unreadable.
|
||||||
|
// Transient failures are normal: the Windows sampler cannot read the cursor
|
||||||
|
// across a desktop switch or while the secure desktop is up, and recovers by
|
||||||
|
// the next capture.
|
||||||
|
const cursorSourceFailureLimit = 10
|
||||||
|
|
||||||
// pendingCursorRect returns the Cursor pseudo-rect for the current sprite
|
// pendingCursorRect returns the Cursor pseudo-rect for the current sprite
|
||||||
// when the client negotiated the encoding and the platform exposes a
|
// when the client negotiated the encoding and the platform exposes a
|
||||||
// cursor source whose serial has changed since the last emission. A nil
|
// cursor source whose serial has changed since the last emission. A nil
|
||||||
@@ -46,11 +53,18 @@ func (s *session) pendingCursorRect(pf clientPixelFormat) []byte {
|
|||||||
img, hotX, hotY, serial, err := src.Cursor()
|
img, hotX, hotY, serial, err := src.Cursor()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.encMu.Lock()
|
s.encMu.Lock()
|
||||||
s.cursorSourceFailed = true
|
s.cursorSourceFailures++
|
||||||
|
failures := s.cursorSourceFailures
|
||||||
|
if failures >= cursorSourceFailureLimit {
|
||||||
|
s.cursorSourceFailed = true
|
||||||
|
}
|
||||||
s.encMu.Unlock()
|
s.encMu.Unlock()
|
||||||
s.log.Debugf("cursor source unavailable: %v", err)
|
s.log.Debugf("cursor source unavailable (%d consecutive): %v", failures, err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
s.encMu.Lock()
|
||||||
|
s.cursorSourceFailures = 0
|
||||||
|
s.encMu.Unlock()
|
||||||
if img == nil {
|
if img == nil {
|
||||||
s.logCursorSkip("no-sprite", "no cursor rect: capturer returned no sprite")
|
s.logCursorSkip("no-sprite", "no cursor rect: capturer returned no sprite")
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
Reference in New Issue
Block a user