/* Linux/glibc X11 capture adapter. Only preload into a leased Sunshine process. * Sunshine resolves XFixesGetCursorImage with dlsym(), so a plain symbol * interposer is insufficient. No system library or desktop theme is replaced. * ABI matches XFixesCursorImage from the Xorg Xfixes.h public header. */ #define _GNU_SOURCE #include #ifdef VDI_NVFBC #include "NvFBC.h" #endif extern void *dlvsym(void *, const char *, const char *); extern char *getenv(const char *); extern int strcmp(const char *, const char *); extern void abort(void); extern int open(const char *, int, ...); extern long write(int, const void *, unsigned long); extern int close(int); typedef struct { short x, y; unsigned short width, height, xhot, yhot; unsigned long serial; unsigned long *pixels; unsigned long atom; const char *name; } CursorImage; typedef void *(*Lookup)(void *, const char *); typedef CursorImage *(*GetImage)(void *); static GetImage real_image; static void receipt(const char *value, unsigned long size) { const char *path = getenv("VDI_CURSOR_RECEIPT"); if (path) { int fd = open(path, 0101 | 01000 | 02000000, 0600); if (fd >= 0) { write(fd, value, size); close(fd); } } } #ifdef VDI_NVFBC static PNVFBCCREATEINSTANCE real_instance; static PNVFBCCREATECAPTURESESSION real_capture; static PNVFBCGETSTATUS real_status; /* Some Optimus cards expose a real NVIDIA X screen without RandR outputs. * Sunshine's enumeration rejects that otherwise capturable screen. Explicit * single-seat mode enumerates the whole 2K X screen, with RandR unavailable, * so Sunshine selects NVFBC_TRACKING_SCREEN instead of an invented output ID. * Never report capture support or dimensions that the driver did not return. */ static NVFBCSTATUS NVFBCAPI screen_status(const NVFBC_SESSION_HANDLE session, NVFBC_GET_STATUS_PARAMS *params) { if (!params || params->dwVersion != NVFBC_GET_STATUS_PARAMS_VER) abort(); PNVFBCGETSTATUS fn = __atomic_load_n(&real_status, __ATOMIC_ACQUIRE); if (!fn) abort(); NVFBCSTATUS status = fn(session, params); if (status == NVFBC_SUCCESS && params->bIsCapturePossible && params->dwOutputNum == 0 && params->screenSize.w == 2560 && params->screenSize.h == 1440) { params->bXRandRAvailable = NVFBC_FALSE; params->dwOutputNum = 1; params->outputs[0] = (NVFBC_RANDR_OUTPUT_INFO){ .dwId = 0, .name = "VDI-X-screen", .trackedBox = {0, 0, 2560, 1440} }; } return status; } static NVFBCSTATUS NVFBCAPI capture_session(const NVFBC_SESSION_HANDLE session, NVFBC_CREATE_CAPTURE_SESSION_PARAMS *params) { if (!params || params->dwVersion != NVFBC_CREATE_CAPTURE_SESSION_PARAMS_VER) abort(); /* A new SDK ABI must be reviewed before suppressing its cursor. */ NVFBC_CREATE_CAPTURE_SESSION_PARAMS local = *params; if (getenv("VDI_CURSOR_LEASE")) local.bWithCursor = NVFBC_FALSE; PNVFBCCREATECAPTURESESSION fn = __atomic_load_n(&real_capture, __ATOMIC_ACQUIRE); if (!fn) abort(); NVFBCSTATUS status = fn(session, &local); if (status == NVFBC_SUCCESS) receipt("nvfbc-v1\n", 9); return status; } static NVFBCSTATUS NVFBCAPI create_instance(NVFBC_API_FUNCTION_LIST *functions) { PNVFBCCREATEINSTANCE fn = __atomic_load_n(&real_instance, __ATOMIC_ACQUIRE); if (!fn) abort(); NVFBCSTATUS status = fn(functions); if (status == NVFBC_SUCCESS) { if (!functions->nvFBCCreateCaptureSession) abort(); __atomic_store_n(&real_capture, functions->nvFBCCreateCaptureSession, __ATOMIC_RELEASE); functions->nvFBCCreateCaptureSession = capture_session; if (getenv("VDI_NVFBC_HEADLESS")) { if (!functions->nvFBCGetStatus) abort(); __atomic_store_n(&real_status, functions->nvFBCGetStatus, __ATOMIC_RELEASE); functions->nvFBCGetStatus = screen_status; } /* Startup probes do not create a capture session until Moonlight connects. */ receipt("nvfbc-ready-v1\n", 15); } return status; } #endif static CursorImage *capture_image(void *display) { GetImage fn = __atomic_load_n(&real_image, __ATOMIC_ACQUIRE); if (!fn) abort(); CursorImage *image = fn(display); if (image && image->pixels) { unsigned long n = (unsigned long)image->width * image->height; if (n > 1048576) abort(); for (unsigned long i = 0; i < n; ++i) image->pixels[i] = 0; } return image; } void *dlsym(void *handle, const char *name) { Lookup lookup = (Lookup)dlvsym((void *)-1L, "dlsym", "GLIBC_2.2.5"); if (!lookup) abort(); void *result = lookup(handle, name); if (result && getenv("VDI_CURSOR_LEASE") && strcmp(name, "XFixesGetCursorImage") == 0) { __atomic_store_n(&real_image, (GetImage)result, __ATOMIC_RELEASE); receipt("xfixes-v1\n", 10); return (void *)capture_image; } #ifdef VDI_NVFBC if (result && (getenv("VDI_CURSOR_LEASE") || getenv("VDI_NVFBC_HEADLESS")) && strcmp(name, "NvFBCCreateInstance") == 0) { __atomic_store_n(&real_instance, (PNVFBCCREATEINSTANCE)result, __ATOMIC_RELEASE); return (void *)create_instance; } #endif return result; }