Skip to content

Commit 0c898a0

Browse files
authored
Merge pull request #63 from ndsl7109256/vncbackend
Implement VNC backend
2 parents 37cab7f + 6cbe4ae commit 0c898a0

File tree

7 files changed

+348
-1
lines changed

7 files changed

+348
-1
lines changed

Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,14 @@ libtwin.a_files-y += backend/fbdev.c
110110
libtwin.a_files-y += backend/linux_input.c
111111
endif
112112

113+
ifeq ($(CONFIG_BACKEND_VNC), y)
114+
BACKEND = vnc
115+
libtwin.a_files-y += backend/vnc.c
116+
libtwin.a_files-y += src/cursor.c
117+
libtwin.a_cflags-y += $(shell pkg-config --cflags neatvnc aml pixman-1)
118+
TARGET_LIBS += $(shell pkg-config --libs neatvnc aml pixman-1)
119+
endif
120+
113121
# Standalone application
114122

115123
ifeq ($(CONFIG_DEMO_APPLICATIONS), y)

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ and the [SDL2 library](https://www.libsdl.org/).
7070
* macOS: `brew install sdl2 jpeg libpng`
7171
* Ubuntu Linux / Debian: `sudo apt install libsdl2-dev libjpeg-dev libpng-dev`
7272

73+
Please note that the VNC backend is only tested on GNU/Linux, and the prebuilt [neatvnc](https://github.com/any1/neatvnc) package might be outdated. To ensure you have the latest versions, you can build the dependent packages from source by running the script:
74+
```bash
75+
$ tools/build-neatvnc.sh
76+
```
77+
7378
### Configuration
7479

7580
Configure via [Kconfiglib](https://pypi.org/project/kconfiglib/), you should select either SDL
@@ -108,6 +113,14 @@ $ sudo usermod -a -G video $USERNAME
108113

109114
In addition, the framebuffer device can be assigned via the environment variable `FRAMEBUFFER`.
110115

116+
To run demo program with the neat-vnc backend:
117+
118+
```shell
119+
$ ./demo-vnc
120+
```
121+
122+
It would launch the vnc server. You could use any VNC client to connect with given IP address(default is "127.0.0.1") and port (default is 5900), you could assign the IP address via the environment variable `MADO_VNC_HOST` and the port via `MADO_VNC_PORT`
123+
111124
## License
112125

113126
`Mado` is available under a MIT-style license, permitting liberal commercial use.

backend/vnc.c

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
/*
2+
Twin - A Tiny Window System
3+
Copyright (c) 2024 National Cheng Kung University, Taiwan
4+
All rights reserved.
5+
*/
6+
#define AML_UNSTABLE_API 1
7+
#include <aml.h>
8+
#include <assert.h>
9+
#include <neatvnc.h>
10+
#include <pixman.h>
11+
#include <stdlib.h>
12+
#include <string.h>
13+
#include <twin.h>
14+
15+
#include "twin_backend.h"
16+
#include "twin_private.h"
17+
18+
#define SCREEN(x) ((twin_context_t *) x)->screen
19+
#define PRIV(x) ((twin_vnc_t *) ((twin_context_t *) x)->priv)
20+
#define MADO_VNC_HOST "MADO_VNC_HOST"
21+
#define MADO_VNC_PORT "MADO_VNC_PORT"
22+
#define MADO_VNC_HOST_DEFAULT "127.0.0.1"
23+
#define MADO_VNC_PORT_DEFAULT "5900"
24+
25+
#ifndef DRM_FORMAT_ARGB8888
26+
#define fourcc_code(a, b, c, d) \
27+
((uint32_t) (a) | ((uint32_t) (b) << 8) | ((uint32_t) (c) << 16) | \
28+
((uint32_t) (d) << 24))
29+
#define DRM_FORMAT_ARGB8888 fourcc_code('A', 'R', '2', '4')
30+
#endif
31+
32+
typedef struct {
33+
twin_screen_t *screen;
34+
struct aml *aml;
35+
struct aml_handler *aml_handler;
36+
struct nvnc *server;
37+
struct nvnc_display *display;
38+
struct nvnc_fb *current_fb;
39+
struct pixman_region16 damage_region;
40+
uint32_t *framebuffer;
41+
int width;
42+
int height;
43+
} twin_vnc_t;
44+
45+
typedef struct {
46+
uint16_t px, py;
47+
enum nvnc_button_mask prev_button;
48+
} twin_peer_t;
49+
50+
#define CURSOR_WIDTH 14
51+
#define CURSOR_HEIGHT 20
52+
53+
static void _twin_vnc_put_begin(twin_coord_t left,
54+
twin_coord_t top,
55+
twin_coord_t right,
56+
twin_coord_t bottom,
57+
void *closure)
58+
{
59+
(void) left;
60+
(void) top;
61+
(void) right;
62+
(void) bottom;
63+
twin_vnc_t *tx = PRIV(closure);
64+
pixman_region_init_rect(&tx->damage_region, 0, 0, tx->width, tx->height);
65+
}
66+
67+
static void _twin_vnc_put_span(twin_coord_t left,
68+
twin_coord_t top,
69+
twin_coord_t right,
70+
twin_argb32_t *pixels,
71+
void *closure)
72+
{
73+
twin_vnc_t *tx = PRIV(closure);
74+
uint32_t *fb_pixels = tx->framebuffer + top * tx->width + left;
75+
size_t span_width = right - left;
76+
77+
memcpy(fb_pixels, pixels, span_width * sizeof(*fb_pixels));
78+
79+
pixman_region_init_rect(&tx->damage_region, left, top, span_width, 1);
80+
81+
if (pixman_region_not_empty(&tx->damage_region)) {
82+
nvnc_display_feed_buffer(tx->display, tx->current_fb,
83+
&tx->damage_region);
84+
pixman_region_clear(&tx->damage_region);
85+
}
86+
aml_poll(tx->aml, 0);
87+
aml_dispatch(tx->aml);
88+
}
89+
90+
static void twin_vnc_get_screen_size(twin_vnc_t *tx, int *width, int *height)
91+
{
92+
*width = nvnc_fb_get_width(tx->current_fb);
93+
*height = nvnc_fb_get_height(tx->current_fb);
94+
}
95+
96+
static bool _twin_vnc_work(void *closure)
97+
{
98+
twin_screen_t *screen = SCREEN(closure);
99+
100+
if (twin_screen_damaged(screen))
101+
twin_screen_update(screen);
102+
return true;
103+
}
104+
105+
static void _twin_vnc_new_client(struct nvnc_client *client)
106+
{
107+
twin_peer_t *peer = malloc(sizeof(twin_peer_t));
108+
nvnc_set_userdata(client, peer, NULL);
109+
}
110+
111+
static bool _twin_vnc_read_events(int fd, twin_file_op_t op, void *closure)
112+
{
113+
(void) fd;
114+
(void) op;
115+
(void) closure;
116+
return true;
117+
}
118+
119+
static void _twin_vnc_pointer_event(struct nvnc_client *client,
120+
uint16_t x,
121+
uint16_t y,
122+
enum nvnc_button_mask button)
123+
{
124+
twin_peer_t *peer = nvnc_get_userdata(client);
125+
twin_event_t tev;
126+
if ((button & NVNC_BUTTON_LEFT) &&
127+
!(peer->prev_button & NVNC_BUTTON_LEFT)) {
128+
tev.u.pointer.screen_x = x;
129+
tev.u.pointer.screen_y = y;
130+
tev.kind = TwinEventButtonDown;
131+
tev.u.pointer.button = 1;
132+
} else if (!(button & NVNC_BUTTON_LEFT) &&
133+
(peer->prev_button & NVNC_BUTTON_LEFT)) {
134+
tev.u.pointer.screen_x = x;
135+
tev.u.pointer.screen_y = y;
136+
tev.kind = TwinEventButtonUp;
137+
tev.u.pointer.button = 1;
138+
}
139+
if ((peer->px != x || peer->py != y)) {
140+
peer->px = x;
141+
peer->py = y;
142+
tev.u.pointer.screen_x = x;
143+
tev.u.pointer.screen_y = y;
144+
tev.u.pointer.button = 0;
145+
tev.kind = TwinEventMotion;
146+
}
147+
peer->prev_button = button;
148+
struct nvnc *server = nvnc_client_get_server(client);
149+
twin_vnc_t *tx = nvnc_get_userdata(server);
150+
twin_screen_dispatch(tx->screen, &tev);
151+
}
152+
153+
static struct nvnc_fb *_twin_vnc_create_cursor()
154+
{
155+
struct nvnc_fb *fb = nvnc_fb_new(CURSOR_WIDTH, CURSOR_HEIGHT,
156+
DRM_FORMAT_ARGB8888, CURSOR_WIDTH);
157+
uint32_t *pixels = nvnc_fb_get_addr(fb);
158+
for (int i = 0; i < CURSOR_WIDTH * CURSOR_HEIGHT; i++) {
159+
uint32_t a = _twin_cursor_default[i * 4];
160+
uint32_t r = _twin_cursor_default[i * 4 + 1];
161+
uint32_t g = _twin_cursor_default[i * 4 + 2];
162+
uint32_t b = _twin_cursor_default[i * 4 + 3];
163+
pixels[i] = (a << 24) | (r << 16) | (g << 8) | b;
164+
}
165+
return fb;
166+
}
167+
168+
twin_context_t *twin_vnc_init(int width, int height)
169+
{
170+
twin_context_t *ctx = calloc(1, sizeof(twin_context_t));
171+
if (!ctx)
172+
return NULL;
173+
ctx->priv = calloc(1, sizeof(twin_vnc_t));
174+
if (!ctx->priv) {
175+
free(ctx);
176+
return NULL;
177+
}
178+
179+
twin_vnc_t *tx = ctx->priv;
180+
tx->width = width;
181+
tx->height = height;
182+
183+
tx->aml = aml_new();
184+
if (!tx->aml) {
185+
log_error("Failed to create aml");
186+
goto bail_priv;
187+
}
188+
aml_set_default(tx->aml);
189+
char *vnc_host = getenv(MADO_VNC_HOST);
190+
if (!vnc_host) {
191+
log_info(
192+
"Environment variable $MADO_VNC_HOST not set, use %s by default",
193+
MADO_VNC_HOST_DEFAULT);
194+
vnc_host = MADO_VNC_HOST_DEFAULT;
195+
}
196+
char *vnc_port = getenv(MADO_VNC_PORT);
197+
if (!vnc_port) {
198+
log_info(
199+
"Environment variable $MADO_VNC_PORT not set, use %s by default",
200+
MADO_VNC_PORT_DEFAULT);
201+
vnc_port = MADO_VNC_PORT_DEFAULT;
202+
}
203+
log_info("NeatVNC server IP %s PORT %s", vnc_host, vnc_port);
204+
tx->server = nvnc_open(vnc_host, atoi(vnc_port));
205+
if (!tx->server) {
206+
log_error("Failed to open neatvnc server");
207+
goto bail_aml;
208+
}
209+
210+
tx->display = nvnc_display_new(0, 0);
211+
if (!tx->display) {
212+
log_error("Failed to create neatvnc display");
213+
goto bail_server;
214+
}
215+
216+
nvnc_add_display(tx->server, tx->display);
217+
nvnc_set_name(tx->server, "Twin VNC Backend");
218+
nvnc_set_pointer_fn(tx->server, _twin_vnc_pointer_event);
219+
nvnc_set_new_client_fn(tx->server, _twin_vnc_new_client);
220+
nvnc_set_userdata(tx->server, tx, NULL);
221+
struct nvnc_fb *cursor = _twin_vnc_create_cursor();
222+
nvnc_set_cursor(tx->server, cursor, CURSOR_WIDTH, CURSOR_HEIGHT, 0, 0,
223+
true);
224+
nvnc_fb_unref(cursor);
225+
226+
ctx->screen = twin_screen_create(width, height, _twin_vnc_put_begin,
227+
_twin_vnc_put_span, ctx);
228+
if (!ctx->screen)
229+
goto bail_display;
230+
231+
tx->framebuffer = calloc(width * height, sizeof(uint32_t));
232+
if (!tx->framebuffer) {
233+
log_error("Failed to allocate framebuffer");
234+
goto bail_screen;
235+
}
236+
237+
tx->current_fb = nvnc_fb_from_buffer(tx->framebuffer, width, height,
238+
DRM_FORMAT_ARGB8888, width);
239+
if (!tx->current_fb) {
240+
log_error("Failed to init VNC framebuffer");
241+
goto bail_framebuffer;
242+
}
243+
int aml_fd = aml_get_fd(tx->aml);
244+
twin_set_file(_twin_vnc_read_events, aml_fd, TWIN_READ, tx);
245+
246+
twin_set_work(_twin_vnc_work, TWIN_WORK_REDISPLAY, ctx);
247+
tx->screen = ctx->screen;
248+
249+
return ctx;
250+
251+
bail_framebuffer:
252+
free(tx->framebuffer);
253+
bail_screen:
254+
twin_screen_destroy(ctx->screen);
255+
bail_display:
256+
nvnc_display_unref(tx->display);
257+
bail_server:
258+
nvnc_close(tx->server);
259+
bail_aml:
260+
aml_unref(tx->aml);
261+
bail_priv:
262+
free(ctx->priv);
263+
free(ctx);
264+
return NULL;
265+
}
266+
267+
static void twin_vnc_configure(twin_context_t *ctx)
268+
{
269+
int width, height;
270+
twin_vnc_t *tx = ctx->priv;
271+
twin_vnc_get_screen_size(tx, &width, &height);
272+
twin_screen_resize(ctx->screen, width, height);
273+
}
274+
275+
static void twin_vnc_exit(twin_context_t *ctx)
276+
{
277+
if (!ctx)
278+
return;
279+
280+
twin_vnc_t *tx = PRIV(ctx);
281+
282+
nvnc_display_unref(tx->display);
283+
nvnc_close(tx->server);
284+
aml_unref(tx->aml);
285+
286+
free(ctx->priv);
287+
free(ctx);
288+
}
289+
290+
const twin_backend_t g_twin_backend = {
291+
.init = twin_vnc_init,
292+
.configure = twin_vnc_configure,
293+
.exit = twin_vnc_exit,
294+
};

configs/Kconfig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ config BACKEND_FBDEV
1515
config BACKEND_SDL
1616
bool "SDL video output support"
1717

18+
config BACKEND_VNC
19+
bool "VNC server output support"
1820
endchoice
1921

2022
menu "Features"
@@ -39,6 +41,7 @@ comment "Logging is disabled"
3941
config CURSOR
4042
bool "Manipulate cursor"
4143
default n
44+
depends on !BACKEND_VNC
4245

4346
endmenu
4447

include/twin_private.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,8 @@ static inline int twin_clz(uint32_t v)
619619
}
620620
#endif
621621

622+
extern const uint8_t _twin_cursor_default[];
623+
622624
/* Pattern Matching for C macros.
623625
* https://github.com/pfultz2/Cloak/wiki/C-Preprocessor-tricks,-tips,-and-idioms
624626
*/

src/cursor.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
#include <stddef.h>
1010
#include <twin.h>
1111

12-
static const uint8_t _twin_cursor_default[] = {
12+
#include "twin_private.h"
13+
14+
const uint8_t _twin_cursor_default[] = {
1315
0x19, 0x19, 0x19, 0xb8, 0x1e, 0x1e, 0x1e, 0xc8, 0x00, 0x00, 0x00, 0x13,
1416
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
1517
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

tools/build-neatvnc.sh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/usr/bin/env bash
2+
3+
# Update and install dependencies
4+
sudo apt update
5+
sudo apt install -y meson ninja-build libpixman-1-dev zlib1g-dev libdrm-dev pkg-config
6+
7+
TOP_DIR=$(pwd)
8+
# Clone and build aml
9+
git clone https://github.com/any1/aml && pushd aml
10+
meson -Dprefix=$TOP_DIR/_static --default-library=static build
11+
ninja -C build install
12+
popd
13+
14+
export PKG_CONFIG_PATH=$TOP_DIR/_static/lib/$(uname -m)-linux-gnu/pkgconfig
15+
16+
# Clone and build NeatVNC
17+
git clone https://github.com/any1/neatvnc --depth=1 -b v0.8.1 && pushd neatvnc
18+
meson -Dprefix=$TOP_DIR/_static --default-library=static build
19+
ninja -C build install
20+
popd
21+
22+
# Prompt for PKG_CONFIG_PATH
23+
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."
24+
echo "For example, you can run:"
25+
echo " export PKG_CONFIG_PATH=\$(pwd)/_static/lib/\$(uname -m)-linux-gnu/pkgconfig/"

0 commit comments

Comments
 (0)