kmsvnc/server.c

521 lines
16 KiB
C
Raw Normal View History

2023-04-25 00:54:35 +08:00
#include <rfb/rfb.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <sys/mman.h>
#include <xf86drm.h>
2023-04-25 13:54:52 +08:00
#include <libdrm/i915_drm.h>
#include <libdrm/amdgpu_drm.h>
2023-04-25 00:54:35 +08:00
#include <xf86drmMode.h>
2023-04-25 13:54:52 +08:00
#include <linux/uinput.h>
2023-04-26 15:16:47 +08:00
#include <linux/dma-buf.h>
2023-04-25 13:54:52 +08:00
#include <xkbcommon/xkbcommon.h>
#include <math.h>
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
/* 10 frames per second (if we can) */
#define PICTURE_TIMEOUT (1.0/10.0)
#define UINPUT_ABS_MAX INT16_MAX
2023-04-25 14:37:50 +08:00
#define UINPUT_MAX_KEY 256
2023-04-25 13:54:52 +08:00
struct Vec2d {
int x;
int y;
};
struct Vec2d resolution;
/*
* throttle camera updates
*/
static int TimeToTakePicture() {
static struct timeval now={0,0}, then={0,0};
double elapsed, dnow, dthen;
gettimeofday(&now,NULL);
dnow = now.tv_sec + (now.tv_usec /1000000.0);
dthen = then.tv_sec + (then.tv_usec/1000000.0);
elapsed = dnow - dthen;
if (elapsed > PICTURE_TIMEOUT)
memcpy((char *)&then, (char *)&now, sizeof(struct timeval));
return elapsed > PICTURE_TIMEOUT;
}
static void convert_bgrx_to_rgb(const char* in, int width, int height, char* buff) {
for (int y = 0; y < height; y++)
{
for(int x = 0; x < width; x++)
{
buff[(y*width+x)*4] = in[(y*width+x)*4 + 2];
buff[(y*width+x)*4 + 1] = in[(y*width+x)*4 + 1];
buff[(y*width+x)*4 + 2] = in[(y*width+x)*4];
}
}
}
2023-04-26 15:16:47 +08:00
char *kms_convert_buf = NULL;
size_t kms_convert_buf_len = 0;
static inline void convert_kmsbuf(const int XSTRIPE, const int YSTRIPE, const char* in, int width, int height, char* buff) {
2023-04-25 20:50:52 +08:00
if (width % XSTRIPE) {
return;
}
2023-04-26 15:16:47 +08:00
if (kms_convert_buf_len < width * height * 4) {
if (kms_convert_buf) free(kms_convert_buf);
kms_convert_buf = malloc(width * height * 4);
kms_convert_buf_len = width * height * 4;
}
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++)
{
int sno = (x / XSTRIPE) + (y / YSTRIPE) * (width / XSTRIPE);
int ord = (x % XSTRIPE) + (y % YSTRIPE) * XSTRIPE;
int offset = sno * XSTRIPE * YSTRIPE + ord;
if (offset < height*width) {
memcpy(kms_convert_buf + (x+y*width)*4, in + offset*4, 4);
}
}
}
convert_bgrx_to_rgb(kms_convert_buf, width, height, buff);
}
#define XSTRIPE_INTEL 128
#define YSTRIPE_INTEL 8
#define XSTRIPE_NVIDIA 16
#define YSTRIPE_NVIDIA 128
static void convert_nvidia_kmsbuf(const char* in, int width, int height, char* buff) {
2023-04-26 15:20:17 +08:00
convert_kmsbuf(XSTRIPE_NVIDIA, YSTRIPE_NVIDIA, in, width, height, buff);
2023-04-26 15:16:47 +08:00
}
static void convert_intel_kmsbuf(const char* in, int width, int height, char* buff) {
convert_kmsbuf(XSTRIPE_INTEL, YSTRIPE_INTEL, in, width, height, buff);
}
struct vnc_drm_attr {
void (*sync_start)(int);
void (*sync_end)(int);
void (*convert)(const char*, int, int, char*);
};
static inline void drm_sync(int drmfd, uint64_t flags) {
int ioctl_err;
struct dma_buf_sync sync = {
.flags = flags,
};
if (ioctl_err = ioctl(drmfd, DMA_BUF_IOCTL_SYNC, &sync))
fprintf(stderr, "DRM ioctl error %d on line %d\n", ioctl_err, __LINE__);
}
static void drm_sync_start(int drmfd) {
drm_sync(drmfd, DMA_BUF_SYNC_START | DMA_BUF_SYNC_READ);
}
static void drm_sync_end(int drmfd) {
drm_sync(drmfd, DMA_BUF_SYNC_END | DMA_BUF_SYNC_READ);
}
static void drm_sync_noop(int drmfd) {
}
2023-04-25 20:50:52 +08:00
2023-04-25 13:54:52 +08:00
struct vnc_xkb {
struct xkb_context *ctx;
struct xkb_keymap *map;
};
struct vnc_xkb xkb;
int uinput_fd = 0;
2023-04-25 14:37:50 +08:00
char keystate[UINPUT_MAX_KEY];
2023-04-25 13:54:52 +08:00
static void xkb_init() {
xkb.ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (xkb.ctx == NULL) {
fprintf(stderr, "Failed to create XKB context\n");
exit(1);
}
struct xkb_rule_names names = {
.rules = "",
.model = "",
.layout = "us",
.variant = "",
.options = ""
};
xkb.map = xkb_keymap_new_from_names(xkb.ctx, &names, 0);
if (xkb.map == NULL) {
2023-04-25 14:37:50 +08:00
fprintf(stderr, "Failed to create XKB map\n");
2023-04-25 13:54:52 +08:00
exit(1);
}
2023-04-25 14:37:50 +08:00
//printf("xkb: keymap = %s\n", xkb_keymap_get_as_string(xkb.map, XKB_KEYMAP_USE_ORIGINAL_FORMAT));
2023-04-25 13:54:52 +08:00
}
static void uinput_init() {
struct uinput_setup usetup;
uinput_fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
if (uinput_fd <= 0) {
fprintf(stderr, "Failed to open uinput\n");
exit(1);
}
ioctl(uinput_fd, UI_SET_EVBIT, EV_KEY);
ioctl(uinput_fd, UI_SET_EVBIT, EV_SYN);
2023-04-25 14:37:50 +08:00
for (int i = 0; i < UINPUT_MAX_KEY; i++) {
2023-04-25 13:54:52 +08:00
ioctl(uinput_fd, UI_SET_KEYBIT, i);
}
ioctl(uinput_fd, UI_SET_EVBIT, EV_ABS);
ioctl(uinput_fd, UI_SET_ABSBIT, ABS_X);
ioctl(uinput_fd, UI_SET_ABSBIT, ABS_Y);
ioctl(uinput_fd, UI_SET_KEYBIT, BTN_LEFT);
ioctl(uinput_fd, UI_SET_KEYBIT, BTN_MIDDLE);
ioctl(uinput_fd, UI_SET_KEYBIT, BTN_RIGHT);
ioctl(uinput_fd, UI_SET_EVBIT, EV_REL);
ioctl(uinput_fd, UI_SET_RELBIT, REL_WHEEL);
struct uinput_abs_setup abs;
memset(&abs, 0, sizeof(abs));
abs.absinfo.maximum = UINPUT_ABS_MAX;
abs.absinfo.minimum = 0;
abs.code = ABS_X;
ioctl(uinput_fd, UI_ABS_SETUP, &abs);
abs.code = ABS_Y;
ioctl(uinput_fd, UI_ABS_SETUP, &abs);
memset(&usetup, 0, sizeof(usetup));
usetup.id.bustype = BUS_USB;
usetup.id.vendor = 0x0011;
usetup.id.product = 0x4514;
strcpy(usetup.name, "kmsvnc-device");
ioctl(uinput_fd, UI_DEV_SETUP, &usetup);
ioctl(uinput_fd, UI_DEV_CREATE);
2023-04-25 14:37:50 +08:00
memset(keystate, 0, UINPUT_MAX_KEY);
2023-04-25 13:54:52 +08:00
}
struct key_iter_search {
xkb_keysym_t keysym;
xkb_keycode_t keycode;
xkb_level_index_t level;
};
static void key_iter(struct xkb_keymap *xkb, xkb_keycode_t key, void *data) {
struct key_iter_search *search = data;
if (search->keycode != XKB_KEYCODE_INVALID) {
return; // We are done
}
xkb_level_index_t num_levels = xkb_keymap_num_levels_for_key(xkb, key, 0);
for (xkb_level_index_t i = 0; i < num_levels; i++) {
const xkb_keysym_t *syms;
int num_syms = xkb_keymap_key_get_syms_by_level(xkb, key, 0, i, &syms);
for (int k = 0; k < num_syms; k++) {
if (syms[k] == search->keysym) {
search->keycode = key;
search->level = i;
break;
goto end;
}
}
}
end:
return;
}
static void rfb_key_hook(rfbBool down, rfbKeySym keysym, rfbClientPtr cl) {
struct key_iter_search search = {
.keysym = keysym,
.keycode = XKB_KEYCODE_INVALID,
.level = 0,
};
xkb_keymap_key_for_each(xkb.map, key_iter, &search);
if (search.keycode == XKB_KEYCODE_INVALID) {
fprintf(stderr, "Keysym %04x not found in our keymap\n", keysym);
return;
}
2023-04-25 21:07:04 +08:00
//printf("key %s, keysym %04x, keycode %u\n", down ? "down" : "up", keysym, search.keycode);
2023-04-25 14:37:50 +08:00
if (search.keycode >= UINPUT_MAX_KEY) {
fprintf(stderr, "Keycode %d >= %d\n", search.keycode, UINPUT_MAX_KEY);
2023-04-25 13:54:52 +08:00
return;
}
if (down != keystate[search.keycode]) {
struct input_event ies[] = {
{
.type = EV_KEY,
2023-04-25 14:37:50 +08:00
.code = search.keycode - 8, // magic
2023-04-25 13:54:52 +08:00
.value = down,
.time.tv_sec = 0,
.time.tv_usec = 0,
},
{
.type = EV_SYN,
.code = SYN_REPORT,
.value = 0,
},
};
for (int i = 0; i < ARRAY_SIZE(ies); i++) {
write(uinput_fd, &ies[i], sizeof(ies[0]));
}
keystate[search.keycode] = down;
}
}
static void rfb_ptr_hook(int mask, int screen_x, int screen_y, rfbClientPtr cl) {
2023-04-25 21:07:04 +08:00
//printf("pointer to %d, %d\n", screen_x, screen_y);
2023-04-25 13:54:52 +08:00
float global_x = (float)screen_x;
float global_y = (float)screen_y;
int touch_x = round(global_x / resolution.x * UINPUT_ABS_MAX);
int touch_y = round(global_y / resolution.y * UINPUT_ABS_MAX);
struct input_event ies1[] = {
2023-04-26 01:00:37 +08:00
{
.type = EV_ABS,
.code = ABS_X,
.value = touch_x,
},
{
.type = EV_ABS,
.code = ABS_Y,
.value = touch_y,
},
{
.type = EV_KEY,
.code = BTN_LEFT,
.value = !! (mask & 0b1)
},
{
.type = EV_KEY,
.code = BTN_MIDDLE,
.value = !! (mask & 0b10)
},
{
.type = EV_KEY,
.code = BTN_RIGHT,
.value = !! (mask & 0b100)
},
{
.type = EV_SYN,
.code = SYN_REPORT,
.value = 0,
},
2023-04-25 13:54:52 +08:00
};
for (int i = 0; i < ARRAY_SIZE(ies1); i++) {
write(uinput_fd, &ies1[i], sizeof(ies1[0]));
}
if (mask & 0b11000) {
struct input_event ies2[] = {
2023-04-26 01:00:37 +08:00
{
.type = EV_REL,
.code = REL_WHEEL,
.value = mask & 0b1000 ? 1 : -1,
},
{
.type = EV_SYN,
.code = SYN_REPORT,
.value = 0,
},
2023-04-25 13:54:52 +08:00
};
for (int i = 0; i < ARRAY_SIZE(ies2); i++) {
write(uinput_fd, &ies2[i], sizeof(ies2[0]));
}
2023-04-26 01:00:37 +08:00
}
2023-04-25 13:54:52 +08:00
}
2023-04-25 00:54:35 +08:00
int main(int argc, const char **argv)
{
if (argc < 2) {
printf("not enough arguments\n");
return 1;
}
2023-04-25 13:54:52 +08:00
xkb_init();
uinput_init();
2023-04-25 00:54:35 +08:00
const char *card = argv[1];
const int drmfd = open(card, O_RDONLY);
2023-04-26 15:16:47 +08:00
int primefd = -1;
2023-04-25 00:54:35 +08:00
if (drmfd < 0) {
fprintf(stderr, "card %s open failed: %s\n", card, strerror(errno));
return 1;
}
2023-04-25 13:54:52 +08:00
drmVersionPtr drm_ver = drmGetVersion(drmfd);
2023-04-25 00:54:35 +08:00
int err;
int source_plane = 0;
int source_crtc = 0;
err = drmSetClientCap(drmfd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
if (err < 0) {
perror("Failed to set universal planes capability: primary planes will not be usable");
}
drmModePlane *plane = NULL;
drmModePlaneRes *plane_res = NULL;
2023-04-25 13:54:52 +08:00
drmModeFB *fb = NULL;
2023-04-25 00:54:35 +08:00
if (source_plane > 0) {
plane = drmModeGetPlane(drmfd, source_plane);
if (!plane) {
fprintf(stderr, "Failed to get plane %d: %s\n", source_plane, strerror(errno));
goto cleanup;
}
if (plane->fb_id == 0) {
fprintf(stderr, "Place %d does not have an attached framebuffer\n", source_plane);
}
}
else {
plane_res = drmModeGetPlaneResources(drmfd);
if (!plane_res) {
perror("Failed to get plane resources");
goto cleanup;
}
int i;
for (i = 0; i < plane_res->count_planes; i++) {
plane = drmModeGetPlane(drmfd, plane_res->planes[i]);
if (!plane) {
fprintf(stderr, "Failed to get plane %u: %s\n", plane_res->planes[i], strerror(errno));
continue;
}
printf("Plane %u CRTC %u FB %u\n", plane->plane_id, plane->crtc_id, plane->fb_id);
if ((source_crtc > 0 && plane->crtc_id != source_crtc) || plane->fb_id == 0) {
// Either not connected to the target source CRTC
// or not active.
drmModeFreePlane(plane);
plane = NULL;
continue;
}
break;
}
if (i == plane_res->count_planes) {
if (source_crtc > 0) {
fprintf(stderr, "No usable planes found on CRTC %d\n", source_crtc);
} else {
fprintf(stderr, "No usable planes found\n");
}
goto cleanup;
}
printf("Using plane %u to locate framebuffers\n", plane->plane_id);
}
uint32_t plane_id = plane->plane_id;
2023-04-25 13:54:52 +08:00
fb = drmModeGetFB(drmfd, plane->fb_id);
2023-04-25 00:54:35 +08:00
if (!fb) {
fprintf(stderr, "Failed to get framebuffer %u: %s\n", plane->fb_id, strerror(errno));
goto cleanup;
}
2023-04-25 13:54:52 +08:00
printf("Template framebuffer is %u: %ux%u %ubpp %ub depth %u pitch\n", fb->fb_id, fb->width, fb->height, fb->bpp, fb->depth, fb->pitch);
2023-04-25 14:37:50 +08:00
if (fb->bpp != 32 || fb->depth != 24) {
fprintf(stderr, "Unsupported pixfmt\n");
goto cleanup;
}
2023-04-25 00:54:35 +08:00
2023-04-25 13:54:52 +08:00
if (!fb->handle) {
2023-04-25 00:54:35 +08:00
fprintf(stderr, "No handle set on framebuffer: maybe you need some additional capabilities?\n");
goto cleanup;
}
2023-04-25 13:54:52 +08:00
int ioctl_err = 0;
2023-04-26 15:16:47 +08:00
2023-04-25 13:54:52 +08:00
printf("drm driver is %s\n", drm_ver->name);
2023-04-25 14:37:50 +08:00
char *mapped = NULL;
2023-04-26 15:16:47 +08:00
struct vnc_drm_attr funcs = {
.sync_start = &drm_sync_start,
.sync_end = &drm_sync_end,
.convert = &convert_bgrx_to_rgb,
};
err = drmPrimeHandleToFD(drmfd, fb->handle, O_RDWR, &primefd);
if (err < 0 || primefd < 0) {
perror("Failed to get PRIME fd from framebuffer handle");
goto cleanup;
}
int mmap_fd = primefd;
size_t mmap_size = fb->width * fb->height * 32 / 8;
off_t mmap_offset = 0;
2023-04-25 13:54:52 +08:00
if (strcmp(drm_ver->name, "i915") == 0) {
2023-04-26 15:16:47 +08:00
funcs.convert = &convert_intel_kmsbuf;
}
else if (strcmp(drm_ver->name, "amdgpu") == 0) {
struct drm_gem_flink flink;
flink.handle = fb->handle;
if (ioctl_err = drmIoctl(drmfd, DRM_IOCTL_GEM_FLINK, &flink)) {
2023-04-25 13:54:52 +08:00
fprintf(stderr, "DRM ioctl error %d on line %d\n", ioctl_err, __LINE__);
goto cleanup;
}
2023-04-26 15:16:47 +08:00
struct drm_gem_open open_arg;
open_arg.name = flink.name;
printf("global name = %d\n", flink.name);
if (ioctl_err = drmIoctl(drmfd, DRM_IOCTL_GEM_OPEN, &open_arg)) {
fprintf(stderr, "DRM ioctl error %d on line %d\n", ioctl_err, __LINE__);
goto cleanup;
}
2023-04-25 13:54:52 +08:00
union drm_amdgpu_gem_mmap mmap_arg;
memset(&mmap_arg, 0, sizeof(mmap_arg));
mmap_arg.in.handle = open_arg.handle;
if (ioctl_err = drmIoctl(drmfd, DRM_IOCTL_AMDGPU_GEM_MMAP, &mmap_arg)) {
fprintf(stderr, "DRM ioctl error %d on line %d\n", ioctl_err, __LINE__);
goto cleanup;
}
mmap_size = open_arg.size;
mmap_offset = mmap_arg.out.addr_ptr;
}
2023-04-26 15:16:47 +08:00
else if (strcmp(drm_ver->name, "nvidia-drm") == 0) {
// quirky and slow
funcs.convert = &convert_nvidia_kmsbuf;
}
else if (strcmp(drm_ver->name, "vmwgfx") == 0 || strcmp(drm_ver->name, "vboxvideo") == 0 || strcmp(drm_ver->name, "virtio_gpu") == 0) {
// virgl does not work
//funcs.sync_start = &drm_sync_noop;
//funcs.sync_end = &drm_sync_noop;
}
2023-04-26 00:18:33 +08:00
else {
2023-04-26 15:16:47 +08:00
fprintf(stderr, "Untested drm driver, use at your own risk!\n");
2023-04-25 14:37:50 +08:00
}
2023-04-26 15:16:47 +08:00
2023-04-25 14:37:50 +08:00
if (!mapped) {
2023-04-26 15:16:47 +08:00
printf("mapping with size = %d, offset = %d, fd = %d\n", mmap_size, mmap_offset, mmap_fd);
mapped = mmap(NULL, mmap_size, PROT_READ, MAP_SHARED, mmap_fd, mmap_offset);
2023-04-25 14:37:50 +08:00
if (mapped == MAP_FAILED) {
perror("mmap");
goto cleanup;
}
2023-04-25 00:54:35 +08:00
}
2023-04-25 13:54:52 +08:00
size_t buflen = fb->width * fb->height * 32 / 8;
char *buf = malloc(buflen);
2023-04-26 15:20:17 +08:00
char *buf2 = malloc(buflen);
2023-04-25 13:54:52 +08:00
memset(buf, 0, buflen);
2023-04-26 15:20:17 +08:00
memset(buf2, 0, buflen);
2023-04-25 00:54:35 +08:00
2023-04-25 13:54:52 +08:00
resolution.x = fb->width;
resolution.y = fb->height;
2023-04-25 00:54:35 +08:00
rfbScreenInfoPtr server=rfbGetScreen(0,NULL,fb->width,fb->height,8,3,32/8);
if(!server)
return 1;
2023-04-25 13:54:52 +08:00
server->desktopName = "kmsvnc";
server->frameBuffer = buf;
server->port = 5900;
//server->listenInterface = inet_addr("127.0.0.1");
server->ipv6port = 0;
server->listen6Interface = NULL;
server->alwaysShared = (1==1);
server->kbdAddEvent = rfb_key_hook;
server->ptrAddEvent = rfb_ptr_hook;
rfbInitServer(server);
2023-04-25 21:00:46 +08:00
rfbRunEventLoop(server,-1,TRUE);
2023-04-25 13:54:52 +08:00
while (rfbIsActive(server)) {
if (server->clientHead && TimeToTakePicture()) {
2023-04-26 15:16:47 +08:00
funcs.sync_start(primefd);
2023-04-26 15:20:17 +08:00
memcpy(buf2, mapped, buflen);
2023-04-26 15:16:47 +08:00
funcs.sync_end(primefd);
2023-04-26 15:20:17 +08:00
funcs.convert(buf2, fb->width, fb->height, buf);
2023-04-25 13:54:52 +08:00
rfbMarkRectAsModified(server, 0, 0, fb->width, fb->height);
}
}
2023-04-25 00:54:35 +08:00
cleanup:
2023-04-25 13:54:52 +08:00
if (drm_ver != NULL) drmFreeVersion(drm_ver);
if (fb != NULL) drmModeFreeFB(fb);
if (uinput_fd > 0) {
ioctl(uinput_fd, UI_DEV_DESTROY);
close(uinput_fd);
}
2023-04-26 15:16:47 +08:00
if (primefd > 0) close(primefd);
2023-04-25 00:54:35 +08:00
close(drmfd);
return 1;
2023-04-25 13:54:52 +08:00
}