init
This commit is contained in:
commit
8a71fc150f
10 changed files with 1084 additions and 0 deletions
33
CMakeLists.txt
Normal file
33
CMakeLists.txt
Normal file
|
@ -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)
|
25
README.md
Normal file
25
README.md
Normal file
|
@ -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.
|
356
drm.c
Normal file
356
drm.c
Normal file
|
@ -0,0 +1,356 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
11
drm.h
Normal file
11
drm.h
Normal file
|
@ -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();
|
185
input.c
Normal file
185
input.c
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <linux/uinput.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#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]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
input.h
Normal file
19
input.h
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <rfb/rfb.h>
|
||||||
|
|
||||||
|
#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);
|
77
keymap.c
Normal file
77
keymap.c
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
9
keymap.h
Normal file
9
keymap.h
Normal file
|
@ -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);
|
281
kmsvnc.c
Normal file
281
kmsvnc.c
Normal file
|
@ -0,0 +1,281 @@
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <argp.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
88
kmsvnc.h
Normal file
88
kmsvnc.h
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <rfb/rfb.h>
|
||||||
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
|
||||||
|
#include <xf86drm.h>
|
||||||
|
#include <i915_drm.h>
|
||||||
|
#include <amdgpu_drm.h>
|
||||||
|
#include <xf86drmMode.h>
|
||||||
|
#include <linux/dma-buf.h>
|
||||||
|
|
||||||
|
|
||||||
|
#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;
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue