commit 8a71fc150fe2c212ec51d68e7e790014b5b86f06 Author: Jerry Date: Thu Apr 27 19:35:18 2023 +0800 init diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..bf6b0c6 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.13) +project(kmsvnc LANGUAGES C) + +IF(NOT CMAKE_BUILD_TYPE OR CMAKE_BUILD_TYPE STREQUAL "") + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "" FORCE) +endif() +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +find_package(PkgConfig REQUIRED) +pkg_search_module(LIBDRM REQUIRED libdrm) +pkg_search_module(LIBVNCSERVER REQUIRED libvncserver) +pkg_search_module(XKBCOMMON REQUIRED xkbcommon) + +include(CheckIncludeFiles) +CHECK_INCLUDE_FILES("linux/uinput.h;linux/dma-buf.h" HAVE_LINUX_API_HEADERS) +IF(NOT HAVE_LINUX_API_HEADERS) + message(FATAL_ERROR "linux-api-headers not found") +ENDIF() + +add_executable(kmsvnc kmsvnc.c drm.c input.c keymap.c) +target_include_directories(kmsvnc PUBLIC + ${LIBDRM_INCLUDEDIR} + ${LIBDRM_INCLUDEDIR}/libdrm + ${LIBVNCSERVER_INCLUDEDIR} + ${XKBCOMMON_INCLUDEDIR} +) +target_link_libraries(kmsvnc PUBLIC + m + ${LIBDRM_LIBRARIES} + ${LIBVNCSERVER_LIBRARIES} + ${XKBCOMMON_LIBRARIES} +) +install(TARGETS kmsvnc RUNTIME DESTINATION bin) diff --git a/README.md b/README.md new file mode 100644 index 0000000..f723931 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# kmsvnc + +## Introduction +A VNC server for DRM/KMS capable GNU/Linux devices. +The goal is to simply have a universally working vncserver on X, wayland and even something like kmscon. +Currently in very early stage. + +## Dependencies + * cmake + * libvncserver + * libxkbcommon + * libdrm + +## Building +``` +mkdir build +cd build +cmake .. +make +``` + +## Running +Helps are available via `kmsvnc --help`. +For example, `kmsvnc -p 5901 -b 0.0.0.0 -4 -d /dev/dri/card2` +Note that no security is currently supported. diff --git a/drm.c b/drm.c new file mode 100644 index 0000000..f320995 --- /dev/null +++ b/drm.c @@ -0,0 +1,356 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "drm.h" + +extern struct kmsvnc_data *kmsvnc; + +static void convert_copy(const char *in, int width, int height, char *buff) { + memcpy(buff, in, width * height * 4); +} + +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]; + } + } +} + +static char *kms_convert_buf = NULL; +static size_t kms_convert_buf_len = 0; +static char *kms_cpy_tmp_buf = NULL; +static size_t kms_cpy_tmp_buf_len = 0; +static inline void convert_kmsbuf(const int XSTRIPE, const int YSTRIPE, const char *in, int width, int height, char *buff) +{ + if (width % XSTRIPE) + { + return; + } + if (height % YSTRIPE) + { + int sno = (width / XSTRIPE) + (height / YSTRIPE) * (width / XSTRIPE); + int ord = (width % XSTRIPE) + (height % YSTRIPE) * XSTRIPE; + int max_offset = sno * XSTRIPE * YSTRIPE + ord; + if (kms_cpy_tmp_buf_len < max_offset * 4 + 4) + { + if (kms_cpy_tmp_buf) + free(kms_convert_buf); + kms_cpy_tmp_buf = malloc(max_offset * 4 + 4); + kms_cpy_tmp_buf_len = max_offset * 4 + 4; + } + memcpy(kms_cpy_tmp_buf, in, max_offset * 4 + 4); + in = (const char *)kms_cpy_tmp_buf; + } + 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; + 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 + +void convert_nvidia_kmsbuf(const char *in, int width, int height, char *buff) +{ + convert_kmsbuf(XSTRIPE_NVIDIA, YSTRIPE_NVIDIA, in, width, height, buff); +} +void convert_intel_kmsbuf(const char *in, int width, int height, char *buff) +{ + convert_kmsbuf(XSTRIPE_INTEL, YSTRIPE_INTEL, in, width, height, buff); +} + +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__); + } +} + +void drm_sync_start(int drmfd) +{ + drm_sync(drmfd, DMA_BUF_SYNC_START | DMA_BUF_SYNC_READ); +} +void drm_sync_end(int drmfd) +{ + drm_sync(drmfd, DMA_BUF_SYNC_END | DMA_BUF_SYNC_READ); +} +void drm_sync_noop(int drmfd) +{ +} + +void drm_cleanup() { + if (kmsvnc->drm) { + if (kmsvnc->drm->drm_ver) { + drmFreeVersion(kmsvnc->drm->drm_ver); + kmsvnc->drm->drm_ver = NULL; + } + if (kmsvnc->drm->plane) { + drmModeFreePlane(kmsvnc->drm->plane); + kmsvnc->drm->plane = NULL; + } + if (kmsvnc->drm->mfb) { + drmModeFreeFB(kmsvnc->drm->mfb); + kmsvnc->drm->mfb = NULL; + } + if (kmsvnc->drm->mapped) { + munmap(kmsvnc->drm->mapped, kmsvnc->drm->mmap_size); + kmsvnc->drm->mapped = NULL; + } + if (kmsvnc->drm->prime_fd > 0) { + close(kmsvnc->drm->prime_fd); + kmsvnc->drm->prime_fd = 0; + } + if (kmsvnc->drm->drm_fd > 0) { + close(kmsvnc->drm->drm_fd); + kmsvnc->drm->drm_fd = 0; + } + free(kmsvnc->drm); + kmsvnc->drm = NULL; + } +} + +int drm_open() { + struct kmsvnc_drm_data *drm = malloc(sizeof(struct kmsvnc_drm_data)); + memset(drm, 0, sizeof(struct kmsvnc_drm_data)); + kmsvnc->drm = drm; + + drm->drm_fd = open(kmsvnc->card, O_RDONLY); + if (drm->drm_fd < 0) + { + DRM_FATAL("card %s open failed: %s\n", kmsvnc->card, strerror(errno)); + } + drm->drm_ver = drmGetVersion(drm->drm_fd); + int err = drmSetClientCap(drm->drm_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); + if (err < 0) + { + perror("Failed to set universal planes capability: primary planes will not be usable"); + } + if (drm->source_plane > 0) + { + drm->plane = drmModeGetPlane(drm->drm_fd, drm->source_plane); + if (!drm->plane) + DRM_FATAL("Failed to get plane %d: %s\n", drm->source_plane, strerror(errno)); + if (drm->plane->fb_id == 0) + fprintf(stderr, "Place %d does not have an attached framebuffer\n", drm->source_plane); + } + else + { + drm->plane_res = drmModeGetPlaneResources(drm->drm_fd); + if (!drm->plane_res) + DRM_FATAL("Failed to get plane resources: %s\n", strerror(errno)); + int i; + for (i = 0; i < drm->plane_res->count_planes; i++) + { + drm->plane = drmModeGetPlane(drm->drm_fd, drm->plane_res->planes[i]); + if (!drm->plane) + { + fprintf(stderr, "Failed to get plane %u: %s\n", drm->plane_res->planes[i], strerror(errno)); + continue; + } + printf("Plane %u CRTC %u FB %u\n", drm->plane->plane_id, drm->plane->crtc_id, drm->plane->fb_id); + if ((drm->source_crtc > 0 && drm->plane->crtc_id != drm->source_crtc) || drm->plane->fb_id == 0) + { + // Either not connected to the target source CRTC + // or not active. + drmModeFreePlane(drm->plane); + drm->plane = NULL; + continue; + } + break; + } + if (i == drm->plane_res->count_planes) + { + if (drm->source_crtc > 0) + { + DRM_FATAL("No usable planes found on CRTC %d\n", drm->source_crtc); + } + else + { + DRM_FATAL("No usable planes found\n"); + } + } + printf("Using plane %u to locate framebuffers\n", drm->plane->plane_id); + } + uint32_t plane_id = drm->plane->plane_id; + + drm->mfb = drmModeGetFB(drm->drm_fd, drm->plane->fb_id); + if (!drm->mfb) + { + DRM_FATAL("Failed to get framebuffer %u: %s\n", drm->plane->fb_id, strerror(errno)); + } + printf("Template framebuffer is %u: %ux%u %ubpp %ub depth %u pitch\n", drm->mfb->fb_id, drm->mfb->width, drm->mfb->height, drm->mfb->bpp, drm->mfb->depth, drm->mfb->pitch); + + if (drm->mfb->bpp != BYTES_PER_PIXEL * 8 || drm->mfb->depth != 24) + { + DRM_FATAL("Unsupported pixfmt\n"); + } + + if (!drm->mfb->handle) + { + DRM_FATAL("No handle set on framebuffer: maybe you need some additional capabilities?\n"); + } + + int ioctl_err = 0; + + printf("drm driver is %s\n", drm->drm_ver->name); + + drm->mmap_fd = drm->drm_fd; + drm->mmap_size = drm->mfb->width * drm->mfb->height * BYTES_PER_PIXEL; + drm->funcs = malloc(sizeof(struct kmsvnc_drm_funcs)); + drm->funcs->convert = convert_bgrx_to_rgb; + drm->funcs->sync_start = drm_sync_noop; + drm->funcs->sync_end = drm_sync_noop; + + if (drm_vendors()) return 1; + + return 0; +} + + +static int drm_kmsbuf_prime() { + struct kmsvnc_drm_data *drm = kmsvnc->drm; + + int err = drmPrimeHandleToFD(drm->drm_fd, drm->mfb->handle, O_RDWR, &drm->prime_fd); + if (err < 0 || drm->prime_fd < 0) + { + DRM_FATAL("Failed to get PRIME fd from framebuffer handle"); + } + drm->funcs->sync_start = &drm_sync_start; + drm->funcs->sync_end = &drm_sync_end; + drm->mmap_fd = drm->prime_fd; + return 0; +} + +static int drm_kmsbuf_dumb() { + struct kmsvnc_drm_data *drm = kmsvnc->drm; + + struct drm_gem_flink flink; + flink.handle = drm->mfb->handle; + DRM_IOCTL_MUST(drm->drm_fd, DRM_IOCTL_GEM_FLINK, &flink); + + struct drm_gem_open open_arg; + open_arg.name = flink.name; + DRM_IOCTL_MUST(drm->drm_fd, DRM_IOCTL_GEM_OPEN, &open_arg); + + struct drm_mode_map_dumb mreq; + memset(&mreq, 0, sizeof(mreq)); + mreq.handle = open_arg.handle; + DRM_IOCTL_MUST(drm->drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq); + + drm->mmap_size = open_arg.size; + drm->mmap_offset = mreq.offset; + + drm->funcs->sync_start = &drm_sync_noop; + drm->funcs->sync_end = &drm_sync_noop; + return 0; +} + +int drm_vendors() { + struct kmsvnc_drm_data *drm = kmsvnc->drm; + + char *driver_name; + if (kmsvnc->force_driver) { + printf("using %s instead of %s\n", kmsvnc->force_driver, drm->drm_ver->name); + driver_name = kmsvnc->force_driver; + } + else { + driver_name = drm->drm_ver->name; + } + + if (strcmp(driver_name, "i915") == 0) + { + drm->funcs->convert = &convert_intel_kmsbuf; + if (drm_kmsbuf_prime()) return 1; + } + else if (strcmp(driver_name, "amdgpu") == 0) + { + struct drm_gem_flink flink; + flink.handle = drm->mfb->handle; + DRM_IOCTL_MUST(drm->drm_fd, DRM_IOCTL_GEM_FLINK, &flink); + + struct drm_gem_open open_arg; + open_arg.name = flink.name; + DRM_IOCTL_MUST(drm->drm_fd, DRM_IOCTL_GEM_OPEN, &open_arg); + + union drm_amdgpu_gem_mmap mmap_arg; + memset(&mmap_arg, 0, sizeof(mmap_arg)); + mmap_arg.in.handle = open_arg.handle; + DRM_IOCTL_MUST(drm->drm_fd, DRM_IOCTL_AMDGPU_GEM_MMAP, &mmap_arg); + + drm->mmap_size = open_arg.size; + drm->mmap_offset = mmap_arg.out.addr_ptr; + + drm->funcs->sync_start = &drm_sync_noop; + drm->funcs->sync_end = &drm_sync_noop; + } + else if (strcmp(driver_name, "nvidia-drm") == 0) + { + // quirky and slow + drm->funcs->convert = &convert_nvidia_kmsbuf; + if (drm_kmsbuf_dumb()) return 1; + } + else if (strcmp(driver_name, "vmwgfx") == 0 || + strcmp(driver_name, "vboxvideo") == 0 || + strcmp(driver_name, "virtio_gpu") == 0 + ) + { + // virgl does not work + if (drm_kmsbuf_dumb()) return 1; + } + else if (strcmp(driver_name, "test-prime") == 0) + { + if (drm_kmsbuf_prime()) return 1; + } + else if (strcmp(driver_name, "test-map-dumb") == 0) + { + if (drm_kmsbuf_dumb()) return 1; + } + else + { + fprintf(stderr, "Untested drm driver, use at your own risk!\n"); + if (drm_kmsbuf_dumb()) return 1; + } + + if (!drm->mapped) + { + printf("mapping with size = %d, offset = %d, fd = %d\n", drm->mmap_size, drm->mmap_offset, drm->mmap_fd); + drm->mapped = mmap(NULL, drm->mmap_size, PROT_READ, MAP_SHARED, drm->mmap_fd, drm->mmap_offset); + if (drm->mapped == MAP_FAILED) + { + DRM_FATAL("Failed to mmap: %s\n", strerror(errno)); + } + } + + return 0; +} diff --git a/drm.h b/drm.h new file mode 100644 index 0000000..f2b2bf9 --- /dev/null +++ b/drm.h @@ -0,0 +1,11 @@ +#pragma once + +#include "kmsvnc.h" + +#define DRM_FATAL(...) { fprintf(stderr, __VA_ARGS__); return 1; } +#define DRM_IOCTL_MUST(...) { int e; if (e = drmIoctl(__VA_ARGS__)) DRM_FATAL("DRM ioctl error %d on line %d\n", e, __LINE__) } +#define DRM_IOCTL_MAY(...) { int e; if (e = drmIoctl(__VA_ARGS__)) fprintf(stderr, "DRM ioctl error %d on line %d\n", e, __LINE__); } + +void drm_cleanup(); +int drm_open(); +int drm_vendors(); diff --git a/input.c b/input.c new file mode 100644 index 0000000..60eea70 --- /dev/null +++ b/input.c @@ -0,0 +1,185 @@ +#include +#include +#include +#include +#include + +#include "input.h" +#include "keymap.h" + +extern struct kmsvnc_data *kmsvnc; + +void uinput_cleanup() +{ + if (kmsvnc->input) { + if (kmsvnc->input->uinput_fd > 0){ + INP_IOCTL_MAY(kmsvnc->input->uinput_fd, UI_DEV_DESTROY); + close(kmsvnc->input->uinput_fd); + kmsvnc->input->uinput_fd = 0; + } + if (kmsvnc->input->keystate){ + free(kmsvnc->input->keystate); + kmsvnc->input->keystate = NULL; + } + free(kmsvnc->input); + kmsvnc->input = NULL; + } +} + +int uinput_init() +{ + struct kmsvnc_input_data *inp = malloc(sizeof(struct kmsvnc_input_data)); + memset(inp, 0, sizeof(struct kmsvnc_input_data)); + kmsvnc->input = inp; + + inp->uinput_fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); + if (inp->uinput_fd <= 0) + { + INP_FATAL("Failed to open uinput\n"); + } + INP_IOCTL_MUST(inp->uinput_fd, UI_SET_EVBIT, EV_KEY); + INP_IOCTL_MUST(inp->uinput_fd, UI_SET_EVBIT, EV_SYN); + for (int i = 0; i < UINPUT_MAX_KEY; i++) + { + INP_IOCTL_MUST(inp->uinput_fd, UI_SET_KEYBIT, i); + } + + INP_IOCTL_MUST(inp->uinput_fd, UI_SET_EVBIT, EV_ABS); + INP_IOCTL_MUST(inp->uinput_fd, UI_SET_ABSBIT, ABS_X); + INP_IOCTL_MUST(inp->uinput_fd, UI_SET_ABSBIT, ABS_Y); + + INP_IOCTL_MUST(inp->uinput_fd, UI_SET_KEYBIT, BTN_LEFT); + INP_IOCTL_MUST(inp->uinput_fd, UI_SET_KEYBIT, BTN_MIDDLE); + INP_IOCTL_MUST(inp->uinput_fd, UI_SET_KEYBIT, BTN_RIGHT); + INP_IOCTL_MUST(inp->uinput_fd, UI_SET_EVBIT, EV_REL); + INP_IOCTL_MUST(inp->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; + INP_IOCTL_MUST(inp->uinput_fd, UI_ABS_SETUP, &abs); + abs.code = ABS_Y; + INP_IOCTL_MUST(inp->uinput_fd, UI_ABS_SETUP, &abs); + + struct uinput_setup usetup; + memset(&usetup, 0, sizeof(usetup)); + usetup.id.bustype = BUS_USB; + usetup.id.vendor = 0x0011; + usetup.id.product = 0x4514; + strcpy(usetup.name, "kmsvnc"); + + INP_IOCTL_MUST(inp->uinput_fd, UI_DEV_SETUP, &usetup); + INP_IOCTL_MUST(inp->uinput_fd, UI_DEV_CREATE); + + inp->keystate = malloc(UINPUT_MAX_KEY); + memset(inp->keystate, 0, UINPUT_MAX_KEY); + + return 0; +} + +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(kmsvnc->keymap->map, key_iter, &search); + if (search.keycode == XKB_KEYCODE_INVALID) + { + fprintf(stderr, "Keysym %04x not found in our keymap\n", keysym); + return; + } + // printf("key %s, keysym %04x, keycode %u\n", down ? "down" : "up", keysym, search.keycode); + if (search.keycode >= UINPUT_MAX_KEY) + { + fprintf(stderr, "Keycode %d >= %d\n", search.keycode, UINPUT_MAX_KEY); + return; + } + if (down != kmsvnc->input->keystate[search.keycode]) + { + struct input_event ies[] = { + { + .type = EV_KEY, + .code = search.keycode - 8, // magic + .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(kmsvnc->input->uinput_fd, &ies[i], sizeof(ies[0])); + } + + kmsvnc->input->keystate[search.keycode] = down; + } +} + +void rfb_ptr_hook(int mask, int screen_x, int screen_y, rfbClientPtr cl) +{ + // printf("pointer to %d, %d\n", screen_x, screen_y); + float global_x = (float)screen_x; + float global_y = (float)screen_y; + int touch_x = round(global_x / kmsvnc->drm->mfb->width * UINPUT_ABS_MAX); + int touch_y = round(global_y / kmsvnc->drm->mfb->height * UINPUT_ABS_MAX); + struct input_event ies1[] = { + { + .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, + }, + }; + for (int i = 0; i < ARRAY_SIZE(ies1); i++) + { + write(kmsvnc->input->uinput_fd, &ies1[i], sizeof(ies1[0])); + } + if (mask & 0b11000) + { + struct input_event ies2[] = { + { + .type = EV_REL, + .code = REL_WHEEL, + .value = mask & 0b1000 ? 1 : -1, + }, + { + .type = EV_SYN, + .code = SYN_REPORT, + .value = 0, + }, + }; + for (int i = 0; i < ARRAY_SIZE(ies2); i++) + { + write(kmsvnc->input->uinput_fd, &ies2[i], sizeof(ies2[0])); + } + } +} diff --git a/input.h b/input.h new file mode 100644 index 0000000..efa7d8b --- /dev/null +++ b/input.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include "kmsvnc.h" + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) + +#define UINPUT_ABS_MAX INT16_MAX +#define UINPUT_MAX_KEY 256 + +#define INP_FATAL(...) { fprintf(stderr, __VA_ARGS__); return 1; } +#define INP_IOCTL_MUST(...) { int e; if (e = ioctl(__VA_ARGS__)) INP_FATAL("uinput ioctl error %d on line %d\n", e, __LINE__) } +#define INP_IOCTL_MAY(...) { int e; if (e = ioctl(__VA_ARGS__)) fprintf(stderr, "uinput ioctl error %d on line %d\n", e, __LINE__); } + +void uinput_cleanup(); +int uinput_init(); +void rfb_key_hook(rfbBool down, rfbKeySym keysym, rfbClientPtr cl); +void rfb_ptr_hook(int mask, int screen_x, int screen_y, rfbClientPtr cl); diff --git a/keymap.c b/keymap.c new file mode 100644 index 0000000..6acd19f --- /dev/null +++ b/keymap.c @@ -0,0 +1,77 @@ +#include +#include +#include +#include + +#include "keymap.h" + +extern struct kmsvnc_data *kmsvnc; + +void xkb_cleanup() { + if (kmsvnc->keymap) { + if (kmsvnc->keymap->map) { + xkb_keymap_unref(kmsvnc->keymap->map); + kmsvnc->keymap->map = NULL; + } + if (kmsvnc->keymap->ctx) { + xkb_context_unref(kmsvnc->keymap->ctx); + kmsvnc->keymap->ctx = NULL; + } + free(kmsvnc->keymap); + kmsvnc->keymap = NULL; + } +} + +int xkb_init() +{ + struct kmsvnc_keymap_data *xkb = malloc(sizeof(struct kmsvnc_keymap_data)); + memset(xkb, 0, sizeof(struct kmsvnc_keymap_data)); + kmsvnc->keymap = xkb; + + xkb->ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (xkb->ctx == NULL) + { + XKB_FATAL("Failed to create XKB context\n"); + } + struct xkb_rule_names names = { + .rules = NULL, + .model = NULL, + .layout = NULL, + .variant = NULL, + .options = NULL, + }; + xkb->map = xkb_keymap_new_from_names(xkb->ctx, &names, 0); + if (xkb->map == NULL) + { + XKB_FATAL("Failed to create XKB keymap\n"); + } + // printf("xkb: keymap string\n%s\n", xkb_keymap_get_as_string(xkb->map, XKB_KEYMAP_USE_ORIGINAL_FORMAT)); + return 0; +} + + +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; + goto end; + } + } + } +end: + return; +} diff --git a/keymap.h b/keymap.h new file mode 100644 index 0000000..bd1ccda --- /dev/null +++ b/keymap.h @@ -0,0 +1,9 @@ +#pragma once + +#include "kmsvnc.h" + +#define XKB_FATAL(...) { fprintf(stderr, __VA_ARGS__); return 1; } + +void xkb_cleanup(); +int xkb_init(); +void key_iter(struct xkb_keymap *xkb, xkb_keycode_t key, void *data); diff --git a/kmsvnc.c b/kmsvnc.c new file mode 100644 index 0000000..5a5187e --- /dev/null +++ b/kmsvnc.c @@ -0,0 +1,281 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kmsvnc.h" +#include "keymap.h" +#include "input.h" +#include "drm.h" + +struct kmsvnc_data *kmsvnc = NULL; + +#define NS_IN_S 1000000000 + +static void between_frames() +{ + static struct timespec now = {0, 0}, then = {0, 0}, tmp = {0, 0}; + + clock_gettime(CLOCK_MONOTONIC, &now); + memcpy((char *)&then, (char *)&tmp, sizeof(struct timespec)); + tmp.tv_nsec += kmsvnc->vnc_opt->sleep_ns; + if (tmp.tv_nsec >= NS_IN_S) + { + tmp.tv_sec++; + tmp.tv_nsec %= NS_IN_S; + } + if (now.tv_sec < tmp.tv_sec || (now.tv_sec == tmp.tv_sec && now.tv_nsec < tmp.tv_nsec)) + { + then.tv_sec = tmp.tv_sec - now.tv_sec; + then.tv_nsec = tmp.tv_nsec - now.tv_nsec; + if (then.tv_nsec < 0) + { + then.tv_sec--; + then.tv_nsec += NS_IN_S; + } + nanosleep(&then, &then); + } + memcpy((char *)&now, (char *)&then, sizeof(struct timespec)); +} + +static void update_screen_buf(char* to, char *from, int width, int height) { + uint64_t *double_pix_from = (uint64_t *)from; + uint64_t *double_pix_to = (uint64_t *)to; + int min_x = INT32_MAX; + int min_y = INT32_MAX; + int max_x = -1; + int max_y = -1; + if (width % 2 == 0) { + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x+=2) { + if (*double_pix_from != *double_pix_to) { + if (x < min_x) { + min_x = x; + } + if (x > max_x) { + max_x = x; + } + if (y < min_y) { + min_y = y; + } + if (y > max_y) { + max_y = y; + } + } + double_pix_from ++; + double_pix_to ++; + } + } + } + max_x = max_x < 0 ? 0 : max_x; + max_y = max_y < 0 ? 0 : max_y; + min_x = min_x > width ? 0 : min_x; + min_y = min_y > height ? 0 : min_y; + + //printf("dirty: %d, %d, %d, %d\n", min_x, min_y, max_x, max_y); + if (max_x || max_y || min_x || min_y) { + memcpy(to, from, width * height * BYTES_PER_PIXEL); + rfbMarkRectAsModified(kmsvnc->server, min_x, min_y, max_x, max_y); + } +} + +static void cleanup() { + if (kmsvnc->keymap) { + xkb_cleanup(); + } + if (kmsvnc->input) { + uinput_cleanup(); + } + if (kmsvnc->drm) { + drm_cleanup(); + } + if (kmsvnc) { + if (kmsvnc->vnc_opt) { + free(kmsvnc->vnc_opt); + kmsvnc->vnc_opt = NULL; + } + if (kmsvnc->buf1) { + free(kmsvnc->buf1); + kmsvnc->buf1 = NULL; + } + if (kmsvnc->buf) { + free(kmsvnc->buf); + kmsvnc->buf = NULL; + } + free(kmsvnc); + kmsvnc = NULL; + } +} + +void signal_handler(int signum){ + if (kmsvnc->shutdown) { + return; + } + kmsvnc->shutdown = 1; + if (kmsvnc->server) { + rfbShutdownServer(kmsvnc->server,TRUE); + } +} + +static struct argp_option kmsvnc_main_options[] = { + {"device", 'd', "/dev/dri/card0", 0, "DRM device"}, + {"force-driver", 0xfefe, "i915", 0, "force a certain driver (for debug)"}, + {"bind", 'b', "0.0.0.0", 0, "Listen on (ipv4 address)"}, + {"bind6", 0xfeff, "::", 0, "Listen on (ipv6 address)"}, + {"port", 'p', "5900", 0, "Listen port"}, + {"disable-ipv6", '4', 0, OPTION_ARG_OPTIONAL, "Disable ipv6"}, + {"fps", 0xff00, "30", 0, "Target frames per second"}, + {"disable-always-shared", 0xff01, 0, OPTION_ARG_OPTIONAL, "Do not always treat incoming connections as shared"}, + {"disable-input", 'i', 0, OPTION_ARG_OPTIONAL, "Disable uinput"}, + {"desktop-name", 'n', "kmsvnc", 0, "Specify vnc desktop name"}, + {0} +}; + +static error_t parse_opt(int key, char *arg, struct argp_state *state) { + int *arg_cout = state->input; + + switch (key) { + case 'd': + argp_usage(state); + kmsvnc->card = arg; + break; + case 0xfefe: + kmsvnc->force_driver = arg; + break; + case 'b': + if (!inet_aton(arg, kmsvnc->vnc_opt->bind)) { + argp_error(state, "invalid ipv4 address %s", arg); + } + break; + case 0xfeff: + kmsvnc->vnc_opt->bind6 = arg; + break; + case 'p': + int port = atoi(arg); + if (port > 0 && port < 65536) { + kmsvnc->vnc_opt->port = port; + } + else { + argp_error(state, "invalid port %s", arg); + } + break; + case '4': + kmsvnc->vnc_opt->disable_ipv6 = 1; + break; + case 0xff00: + int fps = atoi(arg); + if (fps > 0 && fps < 1000) { + kmsvnc->vnc_opt->sleep_ns = NS_IN_S / fps; + } + else { + argp_error(state, "invalid fps %s", arg); + } + break; + case 0xff01: + kmsvnc->vnc_opt->always_shared = 0; + break; + case 'i': + kmsvnc->disable_input = 1; + break; + case 'n': + kmsvnc->vnc_opt->desktop_name = arg; + break; + case ARGP_KEY_ARG: + return ARGP_ERR_UNKNOWN; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +int main(int argc, char **argv) +{ + struct vnc_opt *vncopt = malloc(sizeof(struct vnc_opt)); + memset(vncopt, 0, sizeof(struct vnc_opt)); + + kmsvnc = malloc(sizeof(struct kmsvnc_data)); + memset(kmsvnc, 0, sizeof(struct kmsvnc_data)); + + kmsvnc->vnc_opt = vncopt; + + kmsvnc->card = "/dev/dri/card0"; + kmsvnc->vnc_opt->bind = &(struct in_addr){0}; + kmsvnc->vnc_opt->always_shared = 1; + kmsvnc->vnc_opt->port = 5900; + kmsvnc->vnc_opt->sleep_ns = NS_IN_S / 30; + kmsvnc->vnc_opt->desktop_name = "kmsvnc"; + + static char *args_doc = ""; + static char *doc = "kmsvnc -- vncserver for DRM/KMS capable GNU/Linux devices"; + + struct argp argp = {kmsvnc_main_options, parse_opt, args_doc, doc}; + argp_parse(&argp, argc, argv, 0, 0, NULL); + + const char* XKB_DEFAULT_LAYOUT = getenv("XKB_DEFAULT_LAYOUT"); + if (!XKB_DEFAULT_LAYOUT || strcmp(XKB_DEFAULT_LAYOUT, "") == 0) { + printf("No keyboard layout set from environment variables, use US layout by default\n"); + printf("See https://xkbcommon.org/doc/current/structxkb__rule__names.html\n"); + setenv("XKB_DEFAULT_LAYOUT", "us", 1); + } + + if (!kmsvnc->disable_input) { + if (xkb_init()) { + cleanup(); + return 1; + } + if (uinput_init()) { + cleanup(); + return 1; + } + } + if (drm_open()) { + cleanup(); + return 1; + } + + size_t buflen = kmsvnc->drm->mfb->width * kmsvnc->drm->mfb->height * BYTES_PER_PIXEL; + kmsvnc->buf = malloc(buflen); + memset(kmsvnc->buf, 0, buflen); + kmsvnc->buf1 = malloc(buflen); + memset(kmsvnc->buf1, 0, buflen); + + signal(SIGHUP, &signal_handler); + signal(SIGINT, &signal_handler); + signal(SIGTERM, &signal_handler); + + kmsvnc->server = rfbGetScreen(0, NULL, kmsvnc->drm->mfb->width, kmsvnc->drm->mfb->height, 8, 3, 4); + if (!kmsvnc->server) { + cleanup(); + return 1; + } + kmsvnc->server->desktopName = kmsvnc->vnc_opt->desktop_name; + kmsvnc->server->frameBuffer = kmsvnc->buf; + kmsvnc->server->port = kmsvnc->vnc_opt->port; + kmsvnc->server->listenInterface = kmsvnc->vnc_opt->bind->s_addr; + kmsvnc->server->ipv6port = kmsvnc->vnc_opt->disable_ipv6 ? 0 : kmsvnc->vnc_opt->port; + kmsvnc->server->listen6Interface = kmsvnc->vnc_opt->bind6; + kmsvnc->server->alwaysShared = kmsvnc->vnc_opt->always_shared; + if (!kmsvnc->disable_input) { + kmsvnc->server->kbdAddEvent = rfb_key_hook; + kmsvnc->server->ptrAddEvent = rfb_ptr_hook; + } + rfbInitServer(kmsvnc->server); + rfbRunEventLoop(kmsvnc->server, -1, TRUE); + while (rfbIsActive(kmsvnc->server)) + { + between_frames(); + if (kmsvnc->server->clientHead) + { + kmsvnc->drm->funcs->sync_start(kmsvnc->drm->prime_fd); + kmsvnc->drm->funcs->convert(kmsvnc->drm->mapped, kmsvnc->drm->mfb->width, kmsvnc->drm->mfb->height, kmsvnc->buf1); + kmsvnc->drm->funcs->sync_end(kmsvnc->drm->prime_fd); + update_screen_buf(kmsvnc->buf, kmsvnc->buf1, kmsvnc->drm->mfb->width, kmsvnc->drm->mfb->height); + } + } + cleanup(); + return 0; +} diff --git a/kmsvnc.h b/kmsvnc.h new file mode 100644 index 0000000..9f0263b --- /dev/null +++ b/kmsvnc.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + + +#define BYTES_PER_PIXEL 4 + +struct vnc_opt +{ + int port; + struct in_addr *bind; + char *bind6; + char disable_ipv6; + int sleep_ns; + int always_shared; + char *desktop_name; +}; + +struct kmsvnc_data +{ + char *card; + char *force_driver; + struct vnc_opt *vnc_opt; + char disable_input; + struct kmsvnc_drm_data *drm; + struct kmsvnc_input_data *input; + struct kmsvnc_keymap_data *keymap; + rfbScreenInfoPtr server; + char shutdown; + char *buf; + char *buf1; +}; + + + +struct key_iter_search +{ + xkb_keysym_t keysym; + + xkb_keycode_t keycode; + xkb_level_index_t level; +}; + +struct kmsvnc_keymap_data +{ + struct xkb_context *ctx; + struct xkb_keymap *map; +}; + + +struct kmsvnc_input_data { + int uinput_fd; + char *keystate; +}; + + +struct kmsvnc_drm_funcs +{ + void (*sync_start)(int); + void (*sync_end)(int); + void (*convert)(const char *, int, int, char *); +}; + +struct kmsvnc_drm_data +{ + int drm_fd; + drmVersionPtr drm_ver; + int prime_fd; + int source_plane; + int source_crtc; + drmModePlane *plane; + drmModePlaneRes *plane_res; + drmModeFB *mfb; + u_int32_t plane_id; + int mmap_fd; + size_t mmap_size; + off_t mmap_offset; + char *mapped; + struct kmsvnc_drm_funcs *funcs; +}; +