From 517bea0daffe6852e4957b0afb4734c7920b7df9 Mon Sep 17 00:00:00 2001 From: Viktor Liu Date: Wed, 20 May 2026 13:13:53 +0200 Subject: [PATCH] Collapse X11 DISPLAY/XAUTHORITY auto-detect logs into one line --- client/vnc/server/agent_windows.go | 3 +- client/vnc/server/capture_darwin.go | 35 ++++++++--------- client/vnc/server/capture_fb_linux.go | 1 - client/vnc/server/capture_fb_unix.go | 1 - client/vnc/server/capture_x11.go | 25 +++++++----- client/vnc/server/copyrect.go | 2 +- client/vnc/server/copyrect_test.go | 2 +- client/vnc/server/cursor_windows.go | 8 ++-- client/vnc/server/input_darwin.go | 24 ++++++------ client/vnc/server/input_windows.go | 1 - client/vnc/server/input_x11.go | 15 ++++---- client/vnc/server/metrics_conn.go | 4 +- client/vnc/server/rfb.go | 34 ++++++++-------- client/vnc/server/server.go | 45 ++++++++++++---------- client/vnc/server/server_darwin.go | 2 +- client/vnc/server/session.go | 25 ++++++------ client/vnc/server/session_clipboard.go | 7 ++-- client/vnc/server/session_remote_cursor.go | 11 +++--- client/vnc/server/swizzle.go | 1 - client/vnc/server/tight_test.go | 5 +-- client/vnc/server/virtual_x11.go | 6 +-- 21 files changed, 131 insertions(+), 126 deletions(-) diff --git a/client/vnc/server/agent_windows.go b/client/vnc/server/agent_windows.go index 318076aaa..3dded65fa 100644 --- a/client/vnc/server/agent_windows.go +++ b/client/vnc/server/agent_windows.go @@ -48,7 +48,7 @@ var ( procWTSFreeMemory = wtsapi32.NewProc("WTSFreeMemory") procWTSQuerySessionInformation = wtsapi32.NewProc("WTSQuerySessionInformationW") - iphlpapi = windows.NewLazySystemDLL("iphlpapi.dll") + iphlpapi = windows.NewLazySystemDLL("iphlpapi.dll") procGetExtendedTcpTable = iphlpapi.NewProc("GetExtendedTcpTable") ) @@ -283,7 +283,6 @@ func getSystemTokenForSession(sessionID uint32) (windows.Token, error) { return dup, nil } - // injectEnvVar appends a KEY=VALUE entry to a Unicode environment block. // The block is a sequence of null-terminated UTF-16 strings, terminated by // an extra null. Returns the new []uint16 backing slice; the caller must diff --git a/client/vnc/server/capture_darwin.go b/client/vnc/server/capture_darwin.go index d113f8de3..6b975ed87 100644 --- a/client/vnc/server/capture_darwin.go +++ b/client/vnc/server/capture_darwin.go @@ -22,24 +22,24 @@ import ( var darwinCaptureOnce sync.Once var ( - cgMainDisplayID func() uint32 - cgDisplayPixelsWide func(uint32) uintptr - cgDisplayPixelsHigh func(uint32) uintptr - cgDisplayCreateImage func(uint32) uintptr - cgImageGetWidth func(uintptr) uintptr - cgImageGetHeight func(uintptr) uintptr - cgImageGetBytesPerRow func(uintptr) uintptr - cgImageGetBitsPerPixel func(uintptr) uintptr - cgImageGetDataProvider func(uintptr) uintptr - cgDataProviderCopyData func(uintptr) uintptr - cgImageRelease func(uintptr) - cfDataGetLength func(uintptr) int64 - cfDataGetBytePtr func(uintptr) uintptr - cfRelease func(uintptr) + cgMainDisplayID func() uint32 + cgDisplayPixelsWide func(uint32) uintptr + cgDisplayPixelsHigh func(uint32) uintptr + cgDisplayCreateImage func(uint32) uintptr + cgImageGetWidth func(uintptr) uintptr + cgImageGetHeight func(uintptr) uintptr + cgImageGetBytesPerRow func(uintptr) uintptr + cgImageGetBitsPerPixel func(uintptr) uintptr + cgImageGetDataProvider func(uintptr) uintptr + cgDataProviderCopyData func(uintptr) uintptr + cgImageRelease func(uintptr) + cfDataGetLength func(uintptr) int64 + cfDataGetBytePtr func(uintptr) uintptr + cfRelease func(uintptr) cgRequestScreenCaptureAccess func() bool - cgEventCreate func(uintptr) uintptr - cgEventGetLocation func(uintptr) cgPoint - darwinCaptureReady bool + cgEventCreate func(uintptr) uintptr + cgEventGetLocation func(uintptr) cgPoint + darwinCaptureReady bool ) // cgPoint mirrors CoreGraphics CGPoint: two doubles, 16 bytes, returned @@ -98,7 +98,6 @@ func initDarwinCapture() { }) } - // CGCapturer captures the macOS main display using Core Graphics. type CGCapturer struct { displayID uint32 diff --git a/client/vnc/server/capture_fb_linux.go b/client/vnc/server/capture_fb_linux.go index 9e33a22a2..f0f13e142 100644 --- a/client/vnc/server/capture_fb_linux.go +++ b/client/vnc/server/capture_fb_linux.go @@ -227,4 +227,3 @@ func swizzleFB32(dst []byte, dstStride int, src []byte, srcStride, w, h int, shi } } } - diff --git a/client/vnc/server/capture_fb_unix.go b/client/vnc/server/capture_fb_unix.go index 01981371f..0c2a0dac0 100644 --- a/client/vnc/server/capture_fb_unix.go +++ b/client/vnc/server/capture_fb_unix.go @@ -109,7 +109,6 @@ func (p *FBPoller) ensureCapturerLocked() error { return nil } - var _ ScreenCapturer = (*FBPoller)(nil) var _ captureIntoer = (*FBPoller)(nil) diff --git a/client/vnc/server/capture_x11.go b/client/vnc/server/capture_x11.go index 93ee84ae1..1d373c691 100644 --- a/client/vnc/server/capture_x11.go +++ b/client/vnc/server/capture_x11.go @@ -18,6 +18,12 @@ import ( "github.com/jezek/xgb/xproto" ) +// x11SocketDir is the well-known directory where X servers create their +// abstract UNIX-domain sockets, named "X". Used both for +// auto-detecting an existing display and for placing/probing sockets of +// virtual sessions we spawn. +const x11SocketDir = "/tmp/.X11-unix" + // X11Capturer captures the screen from an X11 display using the MIT-SHM extension. type X11Capturer struct { mu sync.Mutex @@ -26,7 +32,7 @@ type X11Capturer struct { w, h int shmID int shmAddr []byte - shmSeg uint32 // shm.Seg + shmSeg uint32 useSHM bool // bufs double-buffers output images so the X11Poller's capture loop can // overwrite one while the session is still encoding the other. Before @@ -83,7 +89,7 @@ func detectX11FromProc() bool { // detectX11FromSockets checks /tmp/.X11-unix/ for X sockets and uses ps // to find the auth file. Works on FreeBSD and other systems without /proc. func detectX11FromSockets() bool { - entries, err := os.ReadDir("/tmp/.X11-unix") + entries, err := os.ReadDir(x11SocketDir) if err != nil { return false } @@ -96,12 +102,12 @@ func detectX11FromSockets() bool { } display := ":" + name[1:] os.Setenv("DISPLAY", display) - log.Infof("auto-detected DISPLAY=%s (from socket)", display) - - // Try to find -auth from ps output. - if auth := findXorgAuthFromPS(); auth != "" { + auth := findXorgAuthFromPS() + if auth != "" { os.Setenv("XAUTHORITY", auth) - log.Infof("auto-detected XAUTHORITY=%s (from ps)", auth) + log.Infof("auto-detected DISPLAY=%s (from socket) XAUTHORITY=%s (from ps)", display, auth) + } else { + log.Infof("auto-detected DISPLAY=%s (from socket)", display) } return true } @@ -150,11 +156,12 @@ func parseXorgArgs(args []string) (display, auth string) { func setDisplayEnv(display, auth string) { os.Setenv("DISPLAY", display) - log.Infof("auto-detected DISPLAY=%s", display) if auth != "" { os.Setenv("XAUTHORITY", auth) - log.Infof("auto-detected XAUTHORITY=%s", auth) + log.Infof("auto-detected DISPLAY=%s XAUTHORITY=%s", display, auth) + return } + log.Infof("auto-detected DISPLAY=%s", display) } func splitCmdline(data []byte) []string { diff --git a/client/vnc/server/copyrect.go b/client/vnc/server/copyrect.go index 97d2756ae..2e0fb56fd 100644 --- a/client/vnc/server/copyrect.go +++ b/client/vnc/server/copyrect.go @@ -37,7 +37,7 @@ type copyRectDetector struct { cols, rows int // tileHash[ty*cols + tx] is the current hash of the tile at (tx, ty) // in the previous frame. Lookup uses this to detect stale prevTiles - // entries — incremental updates may leave hash→pos entries pointing + // entries: incremental updates may leave hash→pos entries pointing // at a tile whose content has since changed. tileHash []uint64 // prevTiles maps a tile hash to a (x, y) origin in the previous frame. diff --git a/client/vnc/server/copyrect_test.go b/client/vnc/server/copyrect_test.go index 51f0d23b5..0295e6c26 100644 --- a/client/vnc/server/copyrect_test.go +++ b/client/vnc/server/copyrect_test.go @@ -43,7 +43,7 @@ func TestCopyRectDetector_DetectsVerticalScroll(t *testing.T) { fillTile(prev, tx*ts, ty*ts, ts, byte(tx*40), byte(ty*60), 0x80) } } - // cur: simulate a single-tile-row scroll upward — every tile copied from + // cur: simulate a single-tile-row scroll upward, every tile copied from // the row below in prev, top row is new content. for ty := 0; ty < 2; ty++ { for tx := 0; tx < 4; tx++ { diff --git a/client/vnc/server/cursor_windows.go b/client/vnc/server/cursor_windows.go index 85e34d833..9e4c98554 100644 --- a/client/vnc/server/cursor_windows.go +++ b/client/vnc/server/cursor_windows.go @@ -42,10 +42,10 @@ type winPoint struct { } type winCursorInfo struct { - Size uint32 - Flags uint32 - Cursor windows.Handle - PtPos winPoint + Size uint32 + Flags uint32 + Cursor windows.Handle + PtPos winPoint } type winIconInfo struct { diff --git a/client/vnc/server/input_darwin.go b/client/vnc/server/input_darwin.go index eefce934c..2e8542aa8 100644 --- a/client/vnc/server/input_darwin.go +++ b/client/vnc/server/input_darwin.go @@ -104,7 +104,9 @@ var ( userActivityID uint32 preventSleepID uint32 preventSleepHeld bool - preventSleepRef int // refcount across concurrent injectors/sessions + // preventSleepRef tracks the refcount of held assertions across + // concurrent injectors and sessions. + preventSleepRef int darwinInputReady bool darwinEventSource uintptr @@ -503,10 +505,10 @@ func (m *MacInputInjector) postScrollWheel(src uintptr, buttonMask uint16) { } // scrollPixelsPerWheelTick is the pixel delta we post for one VNC wheel -// button event. noVNC accumulates the host wheel/trackpad deltaY and -// emits one press+release per ~10 px, so a real gesture arrives as many -// small events; 20 px per event keeps the resulting macOS scroll fluid -// without overshooting on a single notch. +// button event. Browser-based RFB clients typically emit one press+release +// per ~10 px of host wheel/trackpad motion, so a real gesture arrives as +// many small events; ~20 px per event keeps the resulting macOS scroll +// fluid without overshooting on a single notch. const scrollPixelsPerWheelTick int32 = 22 func (m *MacInputInjector) postMouse(src uintptr, eventType int32, x, y float64, button int32) { @@ -543,8 +545,8 @@ func (m *MacInputInjector) postScroll(src uintptr, deltaY int32) { return } // CGEventCreateScrollWheelEvent(source, units, wheelCount, wheel1delta). - // Pixel units (0) feel smoother under noVNC's "one event per ~10 px of - // host wheel" emission than line units (1) where each event jumps a + // Pixel units (0) feel smoother given the small per-event deltas typical + // of RFB wheel events than line units (1) where each event jumps a // whole line. Variadic C function, pass via SyscallN. r1, _, _ := purego.SyscallN(cgEventCreateScrollWheelEventAddr, src, 0, 1, uintptr(uint32(deltaY))) @@ -568,10 +570,10 @@ func (m *MacInputInjector) SetClipboard(text string) { } // TypeText synthesizes the given text as keystrokes via Core Graphics. -// Used by the dashboard's Paste button so the host clipboard reaches -// the focused remote app even when the app doesn't honor pbpaste-style -// clipboard sync (e.g. login screens, locked-down apps). ASCII printable -// runes only; others are skipped. +// Lets a client push host clipboard content to the focused remote app +// even when the app doesn't honor pbpaste-style clipboard sync (e.g. +// login screens, locked-down apps). ASCII printable runes only; others +// are skipped. func (m *MacInputInjector) TypeText(text string) { wakeDisplay() src := ensureEventSource() diff --git a/client/vnc/server/input_windows.go b/client/vnc/server/input_windows.go index b89ade86c..385317bd7 100644 --- a/client/vnc/server/input_windows.go +++ b/client/vnc/server/input_windows.go @@ -48,7 +48,6 @@ const ( keyeventfScanCode = 0x0008 ) - // maxTypedClipboardChars caps the number of characters we will synthesize as // keystrokes when falling back on the Winlogon desktop. Passwords are short; // a huge clipboard getting typed into the login screen would be surprising. diff --git a/client/vnc/server/input_x11.go b/client/vnc/server/input_x11.go index ca0c75631..e7fbc9a26 100644 --- a/client/vnc/server/input_x11.go +++ b/client/vnc/server/input_x11.go @@ -219,16 +219,15 @@ func (x *X11InputInjector) SetClipboard(text string) { } } -// TypeText synthesizes the given text as keystrokes via XTest. We can -// no longer just stuff the host clipboard with xclip and expect Ctrl+V -// to do the rest, because the Paste button is also used at places where -// the focused application isn't a clipboard-aware one (e.g. a TTY login -// in an X11 session, an SDDM/GDM password field that ignores XSelection, -// or a kiosk app). Typing keystrokes covers all of those. +// TypeText synthesizes the given text as keystrokes via XTest. Used in +// places where the focused application isn't clipboard-aware (e.g. a TTY +// login in an X11 session, an SDDM/GDM password field that ignores +// XSelection, or a kiosk app), so stuffing the X clipboard and relying on +// Ctrl+V would not reach the input. // // Limitation: only ASCII printable characters are typed. Non-ASCII runes // are skipped: a paste workflow for them needs Wayland-aware text input -// or layout introspection that we don't have. +// or layout introspection that this path does not implement. func (x *X11InputInjector) TypeText(text string) { const maxChars = 4096 count := 0 @@ -289,7 +288,7 @@ func (x *X11InputInjector) GetClipboard() string { out, err := cmd.Output() if err != nil { // Exit status 1 just means there is no STRING selection set yet, - // which is the steady state on a fresh Xvfb session — logging it + // which is the steady state on a fresh Xvfb session, logging it // every clipboard poll (2s) floods the trace stream. return "" } diff --git a/client/vnc/server/metrics_conn.go b/client/vnc/server/metrics_conn.go index 2baec750a..60f31101e 100644 --- a/client/vnc/server/metrics_conn.go +++ b/client/vnc/server/metrics_conn.go @@ -26,8 +26,8 @@ type SessionTick struct { } // sessionTickInterval is how often metricsConn emits a SessionTick. One -// second matches noVNC's request cadence so each tick covers roughly one -// FBU round-trip during steady-state activity. +// second covers roughly one FBU round-trip at typical client request +// cadences during steady-state activity. const sessionTickInterval = time.Second // metricsConn wraps a net.Conn and tracks per-session byte / write / FBU diff --git a/client/vnc/server/rfb.go b/client/vnc/server/rfb.go index 97f7908b4..af6ab3114 100644 --- a/client/vnc/server/rfb.go +++ b/client/vnc/server/rfb.go @@ -42,17 +42,17 @@ const ( // clientNetbirdTypeText is a NetBird-specific message that asks the // server to synthesize the given text as keystrokes regardless of the - // active desktop. Used by the dashboard's Paste button to push host - // clipboard content into a Windows secure desktop (Winlogon, UAC), - // where the OS clipboard is isolated. Format mirrors clientCutText: - // 1-byte message type + 3-byte padding + 4-byte length + text bytes. - // The opcode is in the vendor-specific range (>=128). + // active desktop. Lets a client push host clipboard content into a + // Windows secure desktop (Winlogon, UAC), where the OS clipboard is + // isolated. Format mirrors clientCutText: 1-byte message type + 3-byte + // padding + 4-byte length + text bytes. The opcode is in the + // vendor-specific range (>=128). clientNetbirdTypeText = 250 // clientNetbirdShowRemoteCursor toggles "show remote cursor" mode. // When enabled the encoder composites the server cursor sprite into // the captured framebuffer and suppresses the Cursor pseudo-encoding - // so the dashboard sees a single pointer at the remote position. + // so the client sees a single pointer at the remote position. // Wire format: 1-byte msgType + 1-byte enable flag + 6 padding bytes // reserved for future arguments (so the message is fixed-size). clientNetbirdShowRemoteCursor = 251 @@ -71,13 +71,13 @@ const ( // Pseudo-encodings carried over wire as rects with a negative // encoding value. The client advertises supported optional protocol // extensions by listing these in SetEncodings. - pseudoEncCursor = -239 - pseudoEncDesktopSize = -223 - pseudoEncLastRect = -224 - pseudoEncQEMUExtendedKeyEvent = -258 - pseudoEncDesktopName = -307 - pseudoEncExtendedDesktopSize = -308 - pseudoEncExtendedMouseButtons = -316 + pseudoEncCursor = -239 + pseudoEncDesktopSize = -223 + pseudoEncLastRect = -224 + pseudoEncQEMUExtendedKeyEvent = -258 + pseudoEncDesktopName = -307 + pseudoEncExtendedDesktopSize = -308 + pseudoEncExtendedMouseButtons = -316 // Quality/Compression level pseudo-encodings. The client picks one // value from each range to tune JPEG quality and zlib effort. 0 is @@ -405,10 +405,10 @@ func coalesceRects(in [][4]int) [][4]int { // algorithm can be split across small methods without long parameter lists // and to keep each method's cognitive complexity below Sonar's threshold. type rectCoalescer struct { - out [][4]int - prevRowStart, prevRowEnd int - curRowStart int - curY int + out [][4]int + prevRowStart, prevRowEnd int + curRowStart int + curY int } func newRectCoalescer(capacity int) *rectCoalescer { diff --git a/client/vnc/server/server.go b/client/vnc/server/server.go index 29bc49a3c..60b7b51a9 100644 --- a/client/vnc/server/server.go +++ b/client/vnc/server/server.go @@ -32,8 +32,8 @@ const ( ) // RFB security-failure reason codes sent to the client. These prefixes are -// stable so dashboard integrations can branch on them without parsing -// free text. Format: "CODE: human message". +// stable so clients can branch on them without parsing free text. +// Format: "CODE: human message". const ( RejectCodeJWTMissing = "AUTH_JWT_MISSING" RejectCodeJWTExpired = "AUTH_JWT_EXPIRED" @@ -114,10 +114,9 @@ type InputInjector interface { // GetClipboard returns the current system clipboard text. GetClipboard() string // TypeText synthesizes the given text as keystrokes on the active - // desktop. Used by the dashboard's Paste button to push host clipboard - // content into a secure desktop (Winlogon/UAC) where the clipboard is - // isolated. On platforms or sessions without keystroke synthesis it - // may be a no-op. + // desktop. Used to push host clipboard content into a secure desktop + // (Winlogon/UAC) where the clipboard is isolated. On platforms or + // sessions without keystroke synthesis it may be a no-op. TypeText(text string) } @@ -132,10 +131,11 @@ type JWTConfig struct { // connectionHeader is sent by the client before the RFB handshake to specify // the VNC session mode and authenticate. type connectionHeader struct { - mode byte - username string - jwt string - sessionID uint32 // Windows session ID (0 = console/auto) + mode byte + username string + jwt string + // sessionID is the Windows session ID; 0 selects the console session. + sessionID uint32 // width and height request the virtual display geometry for session mode. // Zero means use the default. width uint16 @@ -159,9 +159,11 @@ type Server struct { injector InputInjector serviceMode bool disableAuth bool - localAddr netip.Addr // NetBird WireGuard IP this server is bound to - network netip.Prefix // NetBird overlay network - log *log.Entry + // localAddr is the NetBird WireGuard IP this server is bound to. + localAddr netip.Addr + // network is the NetBird overlay network. + network netip.Prefix + log *log.Entry mu sync.Mutex listener net.Listener @@ -173,7 +175,8 @@ type Server struct { jwtExtractor *nbjwt.ClaimsExtractor authorizer *sshauth.Authorizer netstackNet *netstack.Net - agentToken []byte // raw token bytes for agent-mode auth + // agentToken holds the raw token bytes for agent-mode auth. + agentToken []byte sessionsMu sync.Mutex sessionSeq uint64 @@ -212,14 +215,14 @@ type virtualSessionManager interface { } // New creates a VNC server with the given screen capturer and input injector. -// Authentication is handled by the dashboard JWT exchange after the RFB -// handshake; the protocol-level VNC password scheme is not supported. +// Authentication uses a JWT supplied by the client in the connection +// header; the protocol-level VNC password scheme is not supported. func New(capturer ScreenCapturer, injector InputInjector) *Server { return &Server{ - capturer: capturer, - injector: injector, - authorizer: sshauth.NewAuthorizer(), - log: log.WithField("component", "vnc-server"), + capturer: capturer, + injector: injector, + authorizer: sshauth.NewAuthorizer(), + log: log.WithField("component", "vnc-server"), sessions: make(map[uint64]ActiveSessionInfo), sessionConns: make(map[uint64]net.Conn), } @@ -576,7 +579,7 @@ func (s *Server) handleConnection(conn net.Conn) { 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 dashboard's + // so we skip the Cursor pseudo-encoding and let the client's // local fallback show instead. disableCursor: header.mode == ModeSession, } diff --git a/client/vnc/server/server_darwin.go b/client/vnc/server/server_darwin.go index 357b979b1..161ca7dc6 100644 --- a/client/vnc/server/server_darwin.go +++ b/client/vnc/server/server_darwin.go @@ -27,7 +27,7 @@ func (s *Server) platformSessionManager() virtualSessionManager { // connection to a per-user agent. The agent is spawned lazily on the // first connection (and respawned after a console-user change) via // launchctl asuser, which is the only mechanism that lands a child -// inside the user's Aqua session — where WindowServer and TCC grants +// inside the user's Aqua session, where WindowServer and TCC grants // for screen capture work. func (s *Server) serviceAcceptLoop() { mgr := newDarwinAgentManager(s.ctx) diff --git a/client/vnc/server/session.go b/client/vnc/server/session.go index 9bb063592..11a177f7a 100644 --- a/client/vnc/server/session.go +++ b/client/vnc/server/session.go @@ -106,7 +106,7 @@ type session struct { // 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 - // dashboard via clientNetbirdShowRemoteCursor. + // client via clientNetbirdShowRemoteCursor. showRemoteCursor bool // cursorWarnOnce throttles the diagnostic emitted when remote-cursor // compositing falls back to a no-op (capturer cannot supply a sprite @@ -140,9 +140,9 @@ type session struct { // pointerMu guards the cached last cursor position used by // releaseStickyInput so the disconnect-time button-release event // targets the cursor's current spot instead of warping to (0, 0). - pointerMu sync.Mutex - lastPointerX int - lastPointerY int + pointerMu sync.Mutex + lastPointerX int + lastPointerY int } type fbRequest struct { @@ -226,8 +226,9 @@ func (s *session) handshake() error { } // sendSecurityTypes advertises only secNone. Authentication and access -// control are layered on top by the dashboard JWT exchange after the RFB -// handshake completes, not by the protocol-level password scheme. +// control happen in the NetBird connection header (JWT, mode, username) +// that precedes the RFB handshake, not via the protocol-level password +// scheme. func (s *session) sendSecurityTypes() error { _, err := s.conn.Write([]byte{1, secNone}) return err @@ -502,9 +503,9 @@ func (s *session) handleFBUpdateRequest() error { } // SendDesktopName pushes a DesktopName pseudo-encoded update to the -// client if it advertised support. Used by the server to keep the -// dashboard title in sync with the active session (e.g. username -// changes after login on a virtual session). +// client if it advertised support. Lets the client keep its window title +// in sync with the active session (e.g. username changes after login on +// a virtual session). func (s *session) SendDesktopName(name string) error { s.encMu.RLock() supported := s.clientSupportsDesktopName @@ -601,9 +602,9 @@ var stickyModifierKeysyms = [...]uint32{ 0xffe9, 0xffea, // Alt_L, Alt_R 0xffe7, 0xffe8, // Meta_L, Meta_R 0xffeb, 0xffec, // Super_L, Super_R - 0xff7e, // Mode_switch - 0xfe03, // ISO_Level3_Shift (AltGr) - 0xffe5, // Caps_Lock (release if user dropped mid-press) + 0xff7e, // Mode_switch + 0xfe03, // ISO_Level3_Shift (AltGr) + 0xffe5, // Caps_Lock (release if user dropped mid-press) } // releaseStickyInput synthesizes key-up for modifier keysyms and a diff --git a/client/vnc/server/session_clipboard.go b/client/vnc/server/session_clipboard.go index c4554970f..c31b0b8e4 100644 --- a/client/vnc/server/session_clipboard.go +++ b/client/vnc/server/session_clipboard.go @@ -220,9 +220,10 @@ func (s *session) writeExtClipMessage(payload []byte) error { return err } -// handleTypeText handles the NetBird-specific PasteAndType message used by -// the dashboard's Paste button. Wire format mirrors CutText: 3-byte -// padding + 4-byte length + text bytes. +// handleTypeText handles the NetBird-specific PasteAndType message that +// pushes host clipboard content as synthesized keystrokes, used to reach +// secure desktops where the clipboard is isolated. Wire format mirrors +// CutText: 3-byte padding + 4-byte length + text bytes. func (s *session) handleTypeText() error { var header [7]byte if _, err := io.ReadFull(s.conn, header[:]); err != nil { diff --git a/client/vnc/server/session_remote_cursor.go b/client/vnc/server/session_remote_cursor.go index 2ed77320c..b5bcfc62e 100644 --- a/client/vnc/server/session_remote_cursor.go +++ b/client/vnc/server/session_remote_cursor.go @@ -8,9 +8,9 @@ import ( "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. +// handleShowRemoteCursor handles the NetBird-specific RFB message that +// toggles "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 { @@ -25,9 +25,8 @@ func (s *session) handleShowRemoteCursor() error { } // 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. +// client 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 diff --git a/client/vnc/server/swizzle.go b/client/vnc/server/swizzle.go index e94a933b6..264a2f5a1 100644 --- a/client/vnc/server/swizzle.go +++ b/client/vnc/server/swizzle.go @@ -28,4 +28,3 @@ func swizzleBGRAtoRGBA(dst, src []byte) { dp[i] = 0xFF000000 | (p & 0x0000FF00) | ((p & 0x00FF0000) >> 16) | ((p & 0x000000FF) << 16) } } - diff --git a/client/vnc/server/tight_test.go b/client/vnc/server/tight_test.go index 808c1b01a..926dc7cb7 100644 --- a/client/vnc/server/tight_test.go +++ b/client/vnc/server/tight_test.go @@ -101,12 +101,11 @@ func TestEncodeTightJPEG(t *testing.T) { func TestSampledColorCount(t *testing.T) { uniform := makeUniformImage(64, 64, 0x10, 0x20, 0x30) - if c := sampledColorCountInto(map[uint32]struct{}{},uniform, 0, 0, 64, 64, 32); c != 1 { + if c := sampledColorCountInto(map[uint32]struct{}{}, uniform, 0, 0, 64, 64, 32); c != 1 { t.Fatalf("uniform should be 1 colour, got %d", c) } rnd := makeBenchImage(128, 128, 1) - if c := sampledColorCountInto(map[uint32]struct{}{},rnd, 0, 0, 128, 128, 16); c <= 16 { + if c := sampledColorCountInto(map[uint32]struct{}{}, rnd, 0, 0, 128, 128, 16); c <= 16 { t.Fatalf("random image should exceed colour cap, got %d", c) } } - diff --git a/client/vnc/server/virtual_x11.go b/client/vnc/server/virtual_x11.go index bc2b426c2..2d3f1cbc7 100644 --- a/client/vnc/server/virtual_x11.go +++ b/client/vnc/server/virtual_x11.go @@ -118,7 +118,7 @@ func (vs *VirtualSession) start() error { return err } - socketPath := fmt.Sprintf("/tmp/.X11-unix/X%s", vs.display[1:]) + socketPath := fmt.Sprintf("%s/X%s", x11SocketDir, vs.display[1:]) if err := waitForPath(socketPath, 5*time.Second); err != nil { vs.stopXvfb() return fmt.Errorf("wait for X11 socket %s: %w", socketPath, err) @@ -196,7 +196,7 @@ func (vs *VirtualSession) isAlive() bool { return false } // Verify the X socket still exists on disk. - socketPath := fmt.Sprintf("/tmp/.X11-unix/X%s", display[1:]) + socketPath := fmt.Sprintf("%s/X%s", x11SocketDir, display[1:]) if _, err := os.Stat(socketPath); err != nil { return false } @@ -590,7 +590,7 @@ func bestSessionCandidate(candidates []sessionCandidate) sessionCandidate { func findFreeDisplay() (string, error) { for n := 50; n < 200; n++ { lockFile := fmt.Sprintf("/tmp/.X%d-lock", n) - socketFile := fmt.Sprintf("/tmp/.X11-unix/X%d", n) + socketFile := fmt.Sprintf("%s/X%d", x11SocketDir, n) if _, err := os.Stat(lockFile); err == nil { continue }