-
Notifications
You must be signed in to change notification settings - Fork 19
Implement VNC backend #63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,294 @@ | ||
/* | ||
Twin - A Tiny Window System | ||
Copyright (c) 2024 National Cheng Kung University, Taiwan | ||
All rights reserved. | ||
*/ | ||
#define AML_UNSTABLE_API 1 | ||
#include <aml.h> | ||
#include <assert.h> | ||
#include <neatvnc.h> | ||
#include <pixman.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
#include <twin.h> | ||
|
||
#include "twin_backend.h" | ||
jserv marked this conversation as resolved.
Show resolved
Hide resolved
|
||
#include "twin_private.h" | ||
|
||
#define SCREEN(x) ((twin_context_t *) x)->screen | ||
#define PRIV(x) ((twin_vnc_t *) ((twin_context_t *) x)->priv) | ||
#define MADO_VNC_HOST "MADO_VNC_HOST" | ||
#define MADO_VNC_PORT "MADO_VNC_PORT" | ||
#define MADO_VNC_HOST_DEFAULT "127.0.0.1" | ||
#define MADO_VNC_PORT_DEFAULT "5900" | ||
|
||
#ifndef DRM_FORMAT_ARGB8888 | ||
#define fourcc_code(a, b, c, d) \ | ||
((uint32_t) (a) | ((uint32_t) (b) << 8) | ((uint32_t) (c) << 16) | \ | ||
((uint32_t) (d) << 24)) | ||
#define DRM_FORMAT_ARGB8888 fourcc_code('A', 'R', '2', '4') | ||
#endif | ||
|
||
typedef struct { | ||
twin_screen_t *screen; | ||
struct aml *aml; | ||
struct aml_handler *aml_handler; | ||
struct nvnc *server; | ||
struct nvnc_display *display; | ||
struct nvnc_fb *current_fb; | ||
struct pixman_region16 damage_region; | ||
uint32_t *framebuffer; | ||
int width; | ||
int height; | ||
} twin_vnc_t; | ||
|
||
typedef struct { | ||
uint16_t px, py; | ||
enum nvnc_button_mask prev_button; | ||
} twin_peer_t; | ||
|
||
#define CURSOR_WIDTH 14 | ||
#define CURSOR_HEIGHT 20 | ||
|
||
static void _twin_vnc_put_begin(twin_coord_t left, | ||
twin_coord_t top, | ||
twin_coord_t right, | ||
twin_coord_t bottom, | ||
void *closure) | ||
{ | ||
(void) left; | ||
(void) top; | ||
(void) right; | ||
(void) bottom; | ||
twin_vnc_t *tx = PRIV(closure); | ||
pixman_region_init_rect(&tx->damage_region, 0, 0, tx->width, tx->height); | ||
} | ||
|
||
static void _twin_vnc_put_span(twin_coord_t left, | ||
twin_coord_t top, | ||
twin_coord_t right, | ||
twin_argb32_t *pixels, | ||
void *closure) | ||
{ | ||
twin_vnc_t *tx = PRIV(closure); | ||
uint32_t *fb_pixels = tx->framebuffer + top * tx->width + left; | ||
size_t span_width = right - left; | ||
|
||
memcpy(fb_pixels, pixels, span_width * sizeof(*fb_pixels)); | ||
|
||
pixman_region_init_rect(&tx->damage_region, left, top, span_width, 1); | ||
|
||
if (pixman_region_not_empty(&tx->damage_region)) { | ||
nvnc_display_feed_buffer(tx->display, tx->current_fb, | ||
&tx->damage_region); | ||
pixman_region_clear(&tx->damage_region); | ||
} | ||
aml_poll(tx->aml, 0); | ||
aml_dispatch(tx->aml); | ||
} | ||
|
||
static void twin_vnc_get_screen_size(twin_vnc_t *tx, int *width, int *height) | ||
{ | ||
*width = nvnc_fb_get_width(tx->current_fb); | ||
*height = nvnc_fb_get_height(tx->current_fb); | ||
} | ||
|
||
static bool _twin_vnc_work(void *closure) | ||
{ | ||
twin_screen_t *screen = SCREEN(closure); | ||
|
||
if (twin_screen_damaged(screen)) | ||
twin_screen_update(screen); | ||
return true; | ||
} | ||
|
||
static void _twin_vnc_new_client(struct nvnc_client *client) | ||
{ | ||
twin_peer_t *peer = malloc(sizeof(twin_peer_t)); | ||
nvnc_set_userdata(client, peer, NULL); | ||
} | ||
|
||
static bool _twin_vnc_read_events(int fd, twin_file_op_t op, void *closure) | ||
{ | ||
(void) fd; | ||
(void) op; | ||
(void) closure; | ||
return true; | ||
} | ||
|
||
static void _twin_vnc_pointer_event(struct nvnc_client *client, | ||
uint16_t x, | ||
uint16_t y, | ||
enum nvnc_button_mask button) | ||
{ | ||
twin_peer_t *peer = nvnc_get_userdata(client); | ||
twin_event_t tev; | ||
if ((button & NVNC_BUTTON_LEFT) && | ||
!(peer->prev_button & NVNC_BUTTON_LEFT)) { | ||
tev.u.pointer.screen_x = x; | ||
tev.u.pointer.screen_y = y; | ||
tev.kind = TwinEventButtonDown; | ||
tev.u.pointer.button = 1; | ||
} else if (!(button & NVNC_BUTTON_LEFT) && | ||
(peer->prev_button & NVNC_BUTTON_LEFT)) { | ||
tev.u.pointer.screen_x = x; | ||
tev.u.pointer.screen_y = y; | ||
tev.kind = TwinEventButtonUp; | ||
tev.u.pointer.button = 1; | ||
} | ||
if ((peer->px != x || peer->py != y)) { | ||
peer->px = x; | ||
peer->py = y; | ||
tev.u.pointer.screen_x = x; | ||
tev.u.pointer.screen_y = y; | ||
tev.u.pointer.button = 0; | ||
tev.kind = TwinEventMotion; | ||
} | ||
peer->prev_button = button; | ||
struct nvnc *server = nvnc_client_get_server(client); | ||
twin_vnc_t *tx = nvnc_get_userdata(server); | ||
twin_screen_dispatch(tx->screen, &tev); | ||
} | ||
|
||
jserv marked this conversation as resolved.
Show resolved
Hide resolved
|
||
static struct nvnc_fb *_twin_vnc_create_cursor() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should have a strong reason to create cursor here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since neatvnc provides a cursor feature, we could use it to render the cursor directly in the VNC framebuffer which makes smoother cursor movement. |
||
{ | ||
struct nvnc_fb *fb = nvnc_fb_new(CURSOR_WIDTH, CURSOR_HEIGHT, | ||
DRM_FORMAT_ARGB8888, CURSOR_WIDTH); | ||
uint32_t *pixels = nvnc_fb_get_addr(fb); | ||
for (int i = 0; i < CURSOR_WIDTH * CURSOR_HEIGHT; i++) { | ||
uint32_t a = _twin_cursor_default[i * 4]; | ||
uint32_t r = _twin_cursor_default[i * 4 + 1]; | ||
uint32_t g = _twin_cursor_default[i * 4 + 2]; | ||
uint32_t b = _twin_cursor_default[i * 4 + 3]; | ||
pixels[i] = (a << 24) | (r << 16) | (g << 8) | b; | ||
} | ||
return fb; | ||
} | ||
|
||
twin_context_t *twin_vnc_init(int width, int height) | ||
{ | ||
twin_context_t *ctx = calloc(1, sizeof(twin_context_t)); | ||
if (!ctx) | ||
return NULL; | ||
ctx->priv = calloc(1, sizeof(twin_vnc_t)); | ||
if (!ctx->priv) { | ||
free(ctx); | ||
return NULL; | ||
} | ||
|
||
twin_vnc_t *tx = ctx->priv; | ||
tx->width = width; | ||
tx->height = height; | ||
|
||
tx->aml = aml_new(); | ||
if (!tx->aml) { | ||
log_error("Failed to create aml"); | ||
goto bail_priv; | ||
} | ||
aml_set_default(tx->aml); | ||
char *vnc_host = getenv(MADO_VNC_HOST); | ||
if (!vnc_host) { | ||
log_info( | ||
"Environment variable $MADO_VNC_HOST not set, use %s by default", | ||
MADO_VNC_HOST_DEFAULT); | ||
vnc_host = MADO_VNC_HOST_DEFAULT; | ||
} | ||
char *vnc_port = getenv(MADO_VNC_PORT); | ||
if (!vnc_port) { | ||
log_info( | ||
"Environment variable $MADO_VNC_PORT not set, use %s by default", | ||
MADO_VNC_PORT_DEFAULT); | ||
vnc_port = MADO_VNC_PORT_DEFAULT; | ||
} | ||
log_info("NeatVNC server IP %s PORT %s", vnc_host, vnc_port); | ||
tx->server = nvnc_open(vnc_host, atoi(vnc_port)); | ||
if (!tx->server) { | ||
log_error("Failed to open neatvnc server"); | ||
goto bail_aml; | ||
jserv marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
tx->display = nvnc_display_new(0, 0); | ||
if (!tx->display) { | ||
log_error("Failed to create neatvnc display"); | ||
goto bail_server; | ||
} | ||
|
||
nvnc_add_display(tx->server, tx->display); | ||
nvnc_set_name(tx->server, "Twin VNC Backend"); | ||
nvnc_set_pointer_fn(tx->server, _twin_vnc_pointer_event); | ||
nvnc_set_new_client_fn(tx->server, _twin_vnc_new_client); | ||
nvnc_set_userdata(tx->server, tx, NULL); | ||
struct nvnc_fb *cursor = _twin_vnc_create_cursor(); | ||
nvnc_set_cursor(tx->server, cursor, CURSOR_WIDTH, CURSOR_HEIGHT, 0, 0, | ||
true); | ||
nvnc_fb_unref(cursor); | ||
|
||
ctx->screen = twin_screen_create(width, height, _twin_vnc_put_begin, | ||
_twin_vnc_put_span, ctx); | ||
if (!ctx->screen) | ||
goto bail_display; | ||
|
||
tx->framebuffer = calloc(width * height, sizeof(uint32_t)); | ||
if (!tx->framebuffer) { | ||
log_error("Failed to allocate framebuffer"); | ||
goto bail_screen; | ||
} | ||
|
||
tx->current_fb = nvnc_fb_from_buffer(tx->framebuffer, width, height, | ||
DRM_FORMAT_ARGB8888, width); | ||
if (!tx->current_fb) { | ||
log_error("Failed to init VNC framebuffer"); | ||
goto bail_framebuffer; | ||
} | ||
int aml_fd = aml_get_fd(tx->aml); | ||
twin_set_file(_twin_vnc_read_events, aml_fd, TWIN_READ, tx); | ||
|
||
twin_set_work(_twin_vnc_work, TWIN_WORK_REDISPLAY, ctx); | ||
tx->screen = ctx->screen; | ||
|
||
return ctx; | ||
|
||
bail_framebuffer: | ||
free(tx->framebuffer); | ||
bail_screen: | ||
twin_screen_destroy(ctx->screen); | ||
bail_display: | ||
nvnc_display_unref(tx->display); | ||
bail_server: | ||
nvnc_close(tx->server); | ||
bail_aml: | ||
aml_unref(tx->aml); | ||
bail_priv: | ||
free(ctx->priv); | ||
free(ctx); | ||
return NULL; | ||
} | ||
jserv marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
static void twin_vnc_configure(twin_context_t *ctx) | ||
{ | ||
int width, height; | ||
twin_vnc_t *tx = ctx->priv; | ||
twin_vnc_get_screen_size(tx, &width, &height); | ||
twin_screen_resize(ctx->screen, width, height); | ||
} | ||
|
||
static void twin_vnc_exit(twin_context_t *ctx) | ||
{ | ||
if (!ctx) | ||
return; | ||
|
||
twin_vnc_t *tx = PRIV(ctx); | ||
|
||
nvnc_display_unref(tx->display); | ||
nvnc_close(tx->server); | ||
aml_unref(tx->aml); | ||
|
||
free(ctx->priv); | ||
free(ctx); | ||
} | ||
|
||
const twin_backend_t g_twin_backend = { | ||
.init = twin_vnc_init, | ||
.configure = twin_vnc_configure, | ||
.exit = twin_vnc_exit, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
#!/usr/bin/env bash | ||
|
||
# Update and install dependencies | ||
sudo apt update | ||
sudo apt install -y meson ninja-build libpixman-1-dev zlib1g-dev libdrm-dev pkg-config | ||
|
||
TOP_DIR=$(pwd) | ||
# Clone and build aml | ||
git clone https://github.com/any1/aml && pushd aml | ||
meson -Dprefix=$TOP_DIR/_static --default-library=static build | ||
ninja -C build install | ||
popd | ||
|
||
export PKG_CONFIG_PATH=$TOP_DIR/_static/lib/$(uname -m)-linux-gnu/pkgconfig | ||
|
||
# Clone and build NeatVNC | ||
git clone https://github.com/any1/neatvnc --depth=1 -b v0.8.1 && pushd neatvnc | ||
meson -Dprefix=$TOP_DIR/_static --default-library=static build | ||
ninja -C build install | ||
jserv marked this conversation as resolved.
Show resolved
Hide resolved
|
||
popd | ||
|
||
# Prompt for PKG_CONFIG_PATH | ||
echo "Now, statically-linked libraries of both aml and neatvnc were installed, and you can set PKG_CONFIG_PATH properly for the Mado build system to detect and facilitate." | ||
echo "For example, you can run:" | ||
echo " export PKG_CONFIG_PATH=\$(pwd)/_static/lib/\$(uname -m)-linux-gnu/pkgconfig/" |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In #55, it also uses pixman features. Therefore, whoever merges first, another person needs to pay attention to the adjustment of pixman patch in the Makefile.