132 lines
5.2 KiB
C
132 lines
5.2 KiB
C
/* 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 <stddef.h>
|
|
#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;
|
|
}
|