Skip to content

Commit 6bdbf37

Browse files
committed
Implement VNC backend
Add VNC backend using `neatvnc`[1]. Allow users can view and interact with applications in Mado through VNC-compatible client software. [1]: https://github.com/any1/neatvnc Close #32
1 parent 980bd4c commit 6bdbf37

File tree

4 files changed

+264
-0
lines changed

4 files changed

+264
-0
lines changed

Makefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,13 @@ 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_cflags-y += $(shell pkg-config --cflags neatvnc aml pixman-1)
117+
TARGET_LIBS += $(shell pkg-config --libs neatvnc aml pixman-1)
118+
endif
119+
113120
# Standalone application
114121

115122
ifeq ($(CONFIG_DEMO_APPLICATIONS), y)

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ 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+
For those who want to run demo program with VNC as the backend, it is necessary to install the [aml](https://github.com/any1/aml) and [neatvnc](https://github.com/any1/neatvnc) libraries. Please note that using package managers like apt might install outdated versions of these libraries. To ensure you have the latest versions, we recommend cloning the source code from their respective GitHub repositories and building them with the guide from their README.
74+
7375
### Configuration
7476

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

109111
In addition, the framebuffer device can be assigned via the environment variable `FRAMEBUFFER`.
110112

113+
To run demo program with the neat-vnc backend:
114+
115+
```shell
116+
$./demo-vnc
117+
```
118+
119+
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).
120+
111121
## License
112122

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

backend/vnc.c

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

configs/Kconfig

Lines changed: 2 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"

0 commit comments

Comments
 (0)