├── src ├── jskeycode2x11keycode.h ├── grabber.h ├── encoder.h ├── app.h ├── grabber.c ├── jskeycode2x11keycode.c ├── server.h ├── encoder.c ├── jsmpeg-vnc.c ├── server.c └── app.c ├── .gitignore ├── CMakeLists.txt ├── Dockerfile ├── bin └── client │ ├── index.html │ ├── jsmpg-vnc.js │ └── jsmpg.js └── README.md /src/jskeycode2x11keycode.h: -------------------------------------------------------------------------------- 1 | #ifndef JSKEYCODE2X11KEYCODE_H 2 | #define JSKEYCODE2X11KEYCODE_H 3 | 4 | #include 5 | 6 | KeyCode js_keycode_to_x11keycode(Display *display, unsigned short keycode); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /src/grabber.h: -------------------------------------------------------------------------------- 1 | #ifndef GRABBER_H 2 | #define GRABBER_H 3 | 4 | #include 5 | 6 | typedef struct { 7 | Window window; 8 | Display *display; 9 | 10 | int width; 11 | int height; 12 | 13 | char *pixels; 14 | int pixels_size; 15 | } grabber_t; 16 | 17 | grabber_t *grabber_create(Display *display, Window window); 18 | void grabber_destroy(grabber_t *self); 19 | void *grabber_grab(grabber_t *self); 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/jsmpeg-vnc 2 | 3 | .DS_STORE 4 | 5 | # Object files 6 | *.o 7 | *.ko 8 | *.obj 9 | *.elf 10 | CMakeLists.txt.user 11 | 12 | # Precompiled Headers 13 | *.gch 14 | *.pch 15 | 16 | # Shared objects (inc. Windows DLLs) 17 | *.dll 18 | *.so 19 | *.so.* 20 | *.dylib 21 | 22 | # Executables 23 | *.exe 24 | *.out 25 | *.app 26 | *.i*86 27 | *.x86_64 28 | *.hex 29 | [Bb]uild 30 | [Bb]in 31 | [Dd]ebug 32 | [Rr]elease 33 | libwebsockets 34 | 35 | # Debug files 36 | *.dSYM/ 37 | *.su 38 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.5) 2 | 3 | set(VERSION 1.0.2) 4 | 5 | set(pjname "jsmpeg-vnc") 6 | project (${pjname} VERSION ${VERSION}) 7 | 8 | SET(_HEADERS 9 | src/app.h 10 | src/encoder.h 11 | src/grabber.h 12 | src/jskeycode2x11keycode.h 13 | src/server.h 14 | ) 15 | 16 | SET(_SOURCES 17 | src/app.c 18 | src/encoder.c 19 | src/grabber.c 20 | src/jskeycode2x11keycode.c 21 | src/jsmpeg-vnc.c 22 | src/server.c 23 | ) 24 | 25 | add_executable(${pjname} ${_HEADERS} ${_SOURCES}) 26 | target_link_libraries(${pjname} websockets avcodec avutil swscale X11 Xtst z) 27 | -------------------------------------------------------------------------------- /src/encoder.h: -------------------------------------------------------------------------------- 1 | #ifndef ENCODER_H 2 | #define ENCODER_H 3 | 4 | #include "libavutil/avutil.h" 5 | #include "libavcodec/avcodec.h" 6 | #include "libswscale/swscale.h" 7 | 8 | typedef struct { 9 | AVCodec *codec; 10 | AVCodecContext *context; 11 | AVFrame *frame; 12 | void *frame_buffer; 13 | 14 | int in_width, in_height; 15 | int out_width, out_height; 16 | 17 | AVPacket packet; 18 | struct SwsContext *sws; 19 | } encoder_t; 20 | 21 | 22 | encoder_t *encoder_create(int in_width, int in_height, int out_width, int out_height, int bitrate); 23 | void encoder_destroy(encoder_t *self); 24 | void encoder_encode(encoder_t *self, void *rgb_pixels, void *encoded_data, size_t *encoded_size); 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/app.h: -------------------------------------------------------------------------------- 1 | #ifndef APP_H 2 | #define APP_H 3 | 4 | #include 5 | #include 6 | 7 | #include "encoder.h" 8 | #include "grabber.h" 9 | #include "server.h" 10 | 11 | 12 | #define APP_MOUSE_SPEED 5.0f 13 | #define APP_FRAME_BUFFER_SIZE (1024*1024) 14 | 15 | typedef struct { 16 | encoder_t *encoder; 17 | grabber_t *grabber; 18 | server_t *server; 19 | Display *display; 20 | Window window; 21 | float mouse_speed; 22 | } app_t; 23 | 24 | 25 | app_t *app_create(Display *display, Window window, int port, int bit_rate, int out_width, int out_height); 26 | void app_destroy(app_t *self); 27 | void app_run(app_t *self, int targt_fps); 28 | 29 | int app_on_http_req(app_t *self, struct lws *socket, char *request); 30 | void app_on_connect(app_t *self, struct lws *socket); 31 | void app_on_close(app_t *self, struct lws *socket); 32 | void app_on_message(app_t *self, struct lws *socket, void *data, size_t len); 33 | 34 | double time_since(clock_t start); 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | ENV TZ=Europe/Stockholm 3 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 4 | 5 | RUN apt-get update 6 | RUN apt-get install -y git 7 | RUN apt-get install -y build-essential 8 | RUN apt-get install -y cmake 9 | RUN apt-get install -y libx11-dev 10 | RUN apt-get install -y libavutil-dev 11 | RUN apt-get install -y libavcodec-dev 12 | RUN apt-get install -y libswscale-dev 13 | RUN apt-get install -y libxtst-dev 14 | RUN apt-get install -y libssl-dev 15 | RUN apt-get install -y pkg-config 16 | RUN apt-get install -y zlib1g-dev 17 | 18 | RUN git clone --branch v4.1-stable --depth 1 https://github.com/warmcat/libwebsockets.git 19 | RUN mkdir -p jsmpeg-vnc-linux/libwebsockets 20 | 21 | WORKDIR jsmpeg-vnc-linux/libwebsockets 22 | 23 | RUN cmake /libwebsockets/ 24 | RUN make 25 | RUN make install 26 | RUN ls -l bin/ 27 | 28 | WORKDIR /jsmpeg-vnc-linux/ 29 | 30 | COPY . ./ 31 | RUN cmake . 32 | RUN make 33 | 34 | ENV LD_LIBRARY_PATH /usr/local/lib 35 | 36 | CMD ["/jsmpeg-vnc-linux/jsmpeg-vnc", "-b", "2000", "-s", "640x480", "-f", "39", "desktop"] 37 | 38 | -------------------------------------------------------------------------------- /src/grabber.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "grabber.h" 7 | 8 | grabber_t *grabber_create(Display *display, Window window) { 9 | grabber_t *self = (grabber_t *) malloc(sizeof(grabber_t)); 10 | memset(self, 0, sizeof(grabber_t)); 11 | 12 | XWindowAttributes gwa; 13 | XGetWindowAttributes(display, window, &gwa); 14 | 15 | self->width = gwa.width; 16 | self->height = gwa.height; 17 | self->display = display; 18 | self->window = window; 19 | 20 | self->pixels_size = self->width * self->height * 4; 21 | self->pixels = malloc(self->pixels_size); 22 | 23 | return self; 24 | } 25 | 26 | void grabber_destroy(grabber_t *self) { 27 | if (self != NULL) { 28 | free(self->pixels); 29 | free(self); 30 | } 31 | } 32 | 33 | void *grabber_grab(grabber_t *self) { 34 | XImage *image = XGetImage(self->display, 35 | self->window, 36 | 0, 0, 37 | self->width, self->height, 38 | AllPlanes, 39 | ZPixmap); 40 | 41 | memcpy(self->pixels, image->data, self->pixels_size); 42 | 43 | XDestroyImage(image); 44 | 45 | return self->pixels; 46 | } 47 | -------------------------------------------------------------------------------- /src/jskeycode2x11keycode.c: -------------------------------------------------------------------------------- 1 | /* Transforms JavaScript KeyEvent.keyCodes to X11 keycodes 2 | 3 | https://guacamole.incubator.apache.org/doc/0.9.5/guacamole-common-js/symbols/src/src_main_webapp_modules_Keyboard.js.html 4 | http://www.cl.cam.ac.uk/~mgk25/ucs/keysym2ucs.c 5 | https://api.kde.org/4.12-api/kdenetwork-apidocs/krfb/html/keysym_8h_source.html 6 | https://rffr.de/wp-content/uploads/manuell/de-dvorak-keysym.xmodmap */ 7 | 8 | #include 9 | #include "jskeycode2x11keycode.h" 10 | 11 | KeyCode js_keycode_to_x11keycode(Display *display, unsigned short keycode) { 12 | /* a-z and 0-9 are the same */ 13 | KeySym keysym; 14 | 15 | switch (keycode) { 16 | 17 | case (8): keysym = XK_BackSpace; break; 18 | case (13): keysym = XK_Return; break; 19 | case (16): keysym = XK_Shift_L; break; 20 | case (17): keysym = XK_Control_L; break; 21 | case (18): keysym = XK_Alt_L; break; 22 | case (20): keysym = XK_Caps_Lock; break; 23 | case (27): keysym = XK_Escape; break; 24 | case (37): keysym = XK_Left; break; 25 | case (38): keysym = XK_Up; break; 26 | case (39): keysym = XK_Right; break; 27 | case (40): keysym = XK_Down; break; 28 | default: keysym = keycode; break; 29 | 30 | } 31 | 32 | return XKeysymToKeycode(display, keysym); 33 | } 34 | -------------------------------------------------------------------------------- /src/server.h: -------------------------------------------------------------------------------- 1 | #ifndef SERVER_H 2 | #define SERVER_H 3 | 4 | #include 5 | 6 | typedef struct server_client_t { 7 | struct lws *socket; 8 | struct server_client_t *next; 9 | } client_t; 10 | 11 | client_t *client_insert(client_t **head, struct lws *socket); 12 | void client_remove(client_t **head, struct lws *socket); 13 | #define client_foreach(HEAD, CLIENT) for(client_t *CLIENT = HEAD; CLIENT; CLIENT = CLIENT->next) 14 | 15 | typedef enum { 16 | server_type_text = LWS_WRITE_TEXT, 17 | server_type_binary = LWS_WRITE_BINARY 18 | } server_data_type_t; 19 | 20 | typedef struct server_t { 21 | struct lws_context *context; 22 | size_t buffer_size; 23 | unsigned char *send_buffer_with_padding; 24 | unsigned char *send_buffer; 25 | void *user; 26 | 27 | int port; 28 | struct server_client_t *clients; 29 | 30 | void (*on_connect)(struct server_t *server, struct lws *wsi); 31 | void (*on_message)(struct server_t *server, struct lws *wsi, void *in, size_t len); 32 | void (*on_close)(struct server_t *server, struct lws *wsi); 33 | int (*on_http_req)(struct server_t *server, struct lws *wsi, char *request); 34 | } server_t; 35 | 36 | server_t *server_create(int port, size_t buffer_size); 37 | void server_destroy(server_t *self); 38 | char *server_get_host_address(server_t *self); 39 | char *server_get_client_address(server_t *self, struct lws *wsi); 40 | void server_update(server_t *self); 41 | void server_send(server_t *self, struct lws *socket, void *data, size_t size, server_data_type_t type); 42 | void server_broadcast(server_t *self, void *data, size_t size, server_data_type_t type); 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /bin/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | jsmpeg-vnc 6 | 48 | 49 | 50 | 51 | 52 |
53 |
UP
54 |
DOWN
55 |
RIGHT
56 |
LEFT
57 | 58 |
ESC
59 |
ENTER
60 |
61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/encoder.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "encoder.h" 5 | 6 | #include "libavutil/avutil.h" 7 | #include "libavcodec/avcodec.h" 8 | #include "libswscale/swscale.h" 9 | 10 | encoder_t *encoder_create(int in_width, int in_height, int out_width, int out_height, int bitrate) { 11 | encoder_t *self = (encoder_t *)malloc(sizeof(encoder_t)); 12 | memset(self, 0, sizeof(encoder_t)); 13 | 14 | self->in_width = in_width; 15 | self->in_height = in_height; 16 | self->out_width = out_width; 17 | self->out_height = out_height; 18 | 19 | avcodec_register_all(); 20 | self->codec = avcodec_find_encoder(AV_CODEC_ID_MPEG1VIDEO); 21 | 22 | self->context = avcodec_alloc_context3(self->codec); 23 | self->context->dct_algo = FF_DCT_FASTINT; 24 | self->context->bit_rate = bitrate; 25 | self->context->width = out_width; 26 | self->context->height = out_height; 27 | self->context->time_base.num = 1; 28 | self->context->time_base.den = 30; 29 | self->context->gop_size = 30; 30 | self->context->max_b_frames = 0; 31 | self->context->pix_fmt = AV_PIX_FMT_YUV420P; 32 | 33 | avcodec_open2(self->context, self->codec, NULL); 34 | 35 | self->frame = av_frame_alloc(); 36 | self->frame->format = AV_PIX_FMT_YUV420P; 37 | self->frame->width = out_width; 38 | self->frame->height = out_height; 39 | self->frame->pts = 0; 40 | 41 | int frame_size = avpicture_get_size(AV_PIX_FMT_YUV420P, out_width, out_height); 42 | self->frame_buffer = malloc(frame_size); 43 | avpicture_fill((AVPicture*)self->frame, (uint8_t*)self->frame_buffer, AV_PIX_FMT_YUV420P, out_width, out_height); 44 | 45 | self->sws = sws_getContext( 46 | in_width, in_height, AV_PIX_FMT_RGB32, 47 | out_width, out_height, AV_PIX_FMT_YUV420P, 48 | SWS_FAST_BILINEAR, 0, 0, 0 49 | ); 50 | 51 | return self; 52 | } 53 | 54 | void encoder_destroy(encoder_t *self) { 55 | if( self == NULL ) { return; } 56 | 57 | sws_freeContext(self->sws); 58 | avcodec_close(self->context); 59 | av_free(self->context); 60 | av_free(self->frame); 61 | free(self->frame_buffer); 62 | free(self); 63 | } 64 | 65 | void encoder_encode(encoder_t *self, void *rgb_pixels, void *encoded_data, size_t *encoded_size) { 66 | uint8_t *in_data[1] = {(uint8_t *)rgb_pixels}; 67 | int in_linesize[1] = {self->in_width * 4}; 68 | sws_scale(self->sws, in_data, in_linesize, 0, self->in_height, self->frame->data, self->frame->linesize); 69 | 70 | int available_size = *encoded_size; 71 | *encoded_size = 0; 72 | self->frame->pts++; 73 | 74 | av_init_packet(&self->packet); 75 | int success = 0; 76 | avcodec_encode_video2(self->context, &self->packet, self->frame, &success); 77 | if( success ) { 78 | if( self->packet.size <= available_size ) { 79 | memcpy(encoded_data, self->packet.data, self->packet.size); 80 | *encoded_size = self->packet.size; 81 | } 82 | else { 83 | printf("Frame too large for buffer (size: %d needed: %d)\n", available_size, self->packet.size); 84 | } 85 | } 86 | av_packet_unref(&self->packet); 87 | } 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jsmpeg-vnc-linux 2 | A semi-complete Linux port of [jsmpeg-vnc](https://github.com/phoboslab/jsmpeg-vnc). 3 | 4 | ## Compiling on Ubuntu 20.04 5 | Install the following packages: 6 | 7 | - build-essential 8 | - libx11-dev 9 | - libx11-dev 10 | - libavutil-dev 11 | - libavcodec-dev 12 | - libswscale-dev 13 | - libxtst-dev 14 | - libssl-dev 15 | - pkg-config 16 | - zlib1g-dev 17 | 18 | In addition to this, compile and install [libwebsockets 4.1](https://github.com/warmcat/libwebsockets/tree/v4.1-stable). 19 | 20 | Then, run the following commands in the root directory: 21 | 22 | ``` 23 | cmake . 24 | make 25 | ``` 26 | 27 | See the [Dockerfile](Dockerfile) for an example on how to install the dependencies, compile libwebsockets, and compile jsmpeg-vnc-linux (it won't run out-of-the-box in Docker though, as there is nothing to stream). 28 | 29 | ## Running 30 | 31 | ``` 32 | jsmpeg-vnc [options] 33 | 34 | Options: 35 | -b bitrate in kilobit/s (default: estimated by output size) 36 | -s output size as WxH. E.g: -s 640x480 (default: same as window size) 37 | -f target framerate (default: 60) 38 | -p port (default: 8080) 39 | -c crop area in the captured window as X,Y,W,H. E.g.: -c 200,300,640,480 40 | -i enable/disable remote input. E.g. -i 0 (default: 1) 41 | 42 | Use "desktop" as the window name to capture the whole Desktop. Use "cursor" 43 | to capture the window at the current cursor position. 44 | 45 | Example: 46 | jsmpeg-vnc -b 2000 -s 640x480 -f 30 -p 9006 "Quake 3: Arena" 47 | 48 | To enable mouse lock in the browser (useful for games that require relative 49 | mouse movements, not absolute ones), append "?mouselock" at the target URL 50 | i.e: http://:8080/?mouselock 51 | ``` 52 | (Copied from the [parent project](https://github.com/phoboslab/jsmpeg-vnc)) 53 | 54 | ## About 55 | For a project I was working on I needed a way to stream desktop applications to the web browser. I tried creating a video stream using ffmpeg, but I quickly discovered that support of live video streaming was rather poor using only `