From 8a71fc150fe2c212ec51d68e7e790014b5b86f06 Mon Sep 17 00:00:00 2001 From: Jerry Date: Thu, 27 Apr 2023 19:35:18 +0800 Subject: [PATCH] init --- CMakeLists.txt | 33 +++++ README.md | 25 ++++ drm.c | 356 +++++++++++++++++++++++++++++++++++++++++++++++++ drm.h | 11 ++ input.c | 185 +++++++++++++++++++++++++ input.h | 19 +++ keymap.c | 77 +++++++++++ keymap.h | 9 ++ kmsvnc.c | 281 ++++++++++++++++++++++++++++++++++++++ kmsvnc.h | 88 ++++++++++++ 10 files changed, 1084 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 drm.c create mode 100644 drm.h create mode 100644 input.c create mode 100644 input.h create mode 100644 keymap.c create mode 100644 keymap.h create mode 100644 kmsvnc.c create mode 100644 kmsvnc.h 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; +}; +