From 12f766531002d0086c8cd2ea62a1605db934304a Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Sun, 22 Sep 2019 19:40:04 +0200 Subject: [PATCH] backend-vnc: add VNC support using Neat VNC library This adds basic VNC protocol support using the Neat VNC library (https://github.com/any1/neatvnc). Neat VNC depends on the AML main loop library. The backend makes use of AML's integrated epoll backend and connects AML via file descriptor with the Wayland event loop. This implementation does not support authentication and hardcodes the pixel format currently. Co-authored-by: Philipp Zabel Co-authored-by: Rouven Czerwinski Signed-off-by: Stefan Agner [r.czerwinski@pengutronix.de: - use new (as of 0.5.0) Neat VNC buffer API, with a buffer pool] Signed-off-by: Rouven Czerwinski [p.zabel@pengutronix.de: - transform repaint damage to output coordinates - transform pointer coordinates into global space - check that outputs and heads are in fact ours, see aab722bb1785..060ef82d9360 - track damage across multiple frame buffers - choose pixel format by drm_fourcc, see 8b6c3fe0ad4b - enable ctrl and alt modifiers - fix frame timing to achieve a constant repaint rate - pass initial size explicitly, see f4559b076008 - use resize_output with pixman-renderer, see 55d08f9634e8..84b5d0eb4bee - allow configuring the refresh rate] Signed-off-by: Philipp Zabel --- compositor/main.c | 99 +++ include/libweston/backend-vnc.h | 71 ++ include/libweston/libweston.h | 1 + include/libweston/meson.build | 1 + libweston/backend-vnc/meson.build | 32 + libweston/backend-vnc/vnc.c | 1032 +++++++++++++++++++++++++++++ libweston/compositor.c | 1 + libweston/meson.build | 1 + meson_options.txt | 6 + 9 files changed, 1244 insertions(+) create mode 100644 include/libweston/backend-vnc.h create mode 100644 libweston/backend-vnc/meson.build create mode 100644 libweston/backend-vnc/vnc.c diff --git a/compositor/main.c b/compositor/main.c index 7dcfa7ab..5689458b 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -61,6 +61,7 @@ #include #include #include +#include #include #include #include @@ -653,6 +654,9 @@ usage(int error_code) #if defined(BUILD_RDP_COMPOSITOR) "\t\t\t\trdp-backend.so\n" #endif +#if defined(BUILD_VNC_COMPOSITOR) + "\t\t\t\tvnc-backend.so\n" +#endif #if defined(BUILD_WAYLAND_COMPOSITOR) "\t\t\t\twayland-backend.so\n" #endif @@ -719,6 +723,15 @@ usage(int error_code) "\n"); #endif +#if defined(BUILD_VNC_COMPOSITOR) + fprintf(out, + "Options for vnc-backend.so:\n\n" + " --width=WIDTH\t\tWidth of desktop\n" + " --height=HEIGHT\tHeight of desktop\n" + " --port=PORT\t\tThe port to listen on\n" + "\n"); +#endif + #if defined(BUILD_WAYLAND_COMPOSITOR) fprintf(out, "Options for wayland-backend.so:\n\n" @@ -3098,6 +3111,90 @@ load_rdp_backend(struct weston_compositor *c, return ret; } +static int +vnc_backend_output_configure(struct weston_output *output) +{ + struct wet_compositor *compositor = to_wet_compositor(output->compositor); + struct wet_output_config *parsed_options = compositor->parsed_options; + const struct weston_vnc_output_api *api = weston_vnc_output_get_api(output->compositor); + int width = 640; + int height = 480; + + assert(parsed_options); + + if (!api) { + weston_log("Cannot use weston_vnc_output_api.\n"); + return -1; + } + + if (parsed_options->width) + width = parsed_options->width; + + if (parsed_options->height) + height = parsed_options->height; + + weston_output_set_scale(output, 1); + weston_output_set_transform(output, WL_OUTPUT_TRANSFORM_NORMAL); + + if (api->output_set_size(output, width, height) < 0) { + weston_log("Cannot configure output \"%s\" using weston_vnc_output_api.\n", + output->name); + return -1; + } + weston_log("vnc_backend_output_configure.. Done\n"); + + return 0; +} + + +static void +weston_vnc_backend_config_init(struct weston_vnc_backend_config *config) +{ + config->base.struct_version = WESTON_VNC_BACKEND_CONFIG_VERSION; + config->base.struct_size = sizeof(struct weston_vnc_backend_config); + + config->bind_address = NULL; + config->port = 5900; + config->refresh_rate = VNC_DEFAULT_FREQ; +} + +static int +load_vnc_backend(struct weston_compositor *c, + int *argc, char *argv[], struct weston_config *wc) +{ + struct weston_vnc_backend_config config = {{ 0, }}; + struct weston_config_section *section; + int ret = 0; + + struct wet_output_config *parsed_options = wet_init_parsed_options(c); + if (!parsed_options) + return -1; + + weston_vnc_backend_config_init(&config); + + const struct weston_option vnc_options[] = { + { WESTON_OPTION_INTEGER, "width", 0, &parsed_options->width }, + { WESTON_OPTION_INTEGER, "height", 0, &parsed_options->height }, + { WESTON_OPTION_STRING, "address", 0, &config.bind_address }, + { WESTON_OPTION_INTEGER, "port", 0, &config.port }, + }; + + parse_options(vnc_options, ARRAY_LENGTH(vnc_options), argc, argv); + + wet_set_simple_head_configurator(c, vnc_backend_output_configure); + section = weston_config_get_section(wc, "vnc", NULL, NULL); + weston_config_section_get_int(section, "refresh-rate", + &config.refresh_rate, + VNC_DEFAULT_FREQ); + + ret = weston_compositor_load_backend(c, WESTON_BACKEND_VNC, + &config.base); + + free(config.bind_address); + + return ret; +} + static int x11_backend_output_configure(struct weston_output *output) { @@ -3343,6 +3440,8 @@ load_backend(struct weston_compositor *compositor, const char *backend, return load_headless_backend(compositor, argc, argv, config); else if (strstr(backend, "rdp-backend.so")) return load_rdp_backend(compositor, argc, argv, config); + else if (strstr(backend, "vnc-backend.so")) + return load_vnc_backend(compositor, argc, argv, config); else if (strstr(backend, "drm-backend.so")) return load_drm_backend(compositor, argc, argv, config); else if (strstr(backend, "x11-backend.so")) diff --git a/include/libweston/backend-vnc.h b/include/libweston/backend-vnc.h new file mode 100644 index 00000000..0085df5f --- /dev/null +++ b/include/libweston/backend-vnc.h @@ -0,0 +1,71 @@ +/* + * Copyright © 2019 Stefan Agner + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WESTON_COMPOSITOR_VNC_H +#define WESTON_COMPOSITOR_VNC_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#define WESTON_VNC_OUTPUT_API_NAME "weston_vnc_output_api_v1" +#define VNC_DEFAULT_FREQ 60 + +struct weston_vnc_output_api { + /** Initialize a VNC output with specified width and height. + * + * Returns 0 on success, -1 on failure. + */ + int (*output_set_size)(struct weston_output *output, + int width, int height); +}; + +static inline const struct weston_vnc_output_api * +weston_vnc_output_get_api(struct weston_compositor *compositor) +{ + const void *api; + api = weston_plugin_api_get(compositor, WESTON_VNC_OUTPUT_API_NAME, + sizeof(struct weston_vnc_output_api)); + + return (const struct weston_vnc_output_api *)api; +} + +#define WESTON_VNC_BACKEND_CONFIG_VERSION 1 + +struct weston_vnc_backend_config { + struct weston_backend_config base; + char *bind_address; + int port; + int refresh_rate; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* WESTON_COMPOSITOR_VNC_H */ diff --git a/include/libweston/libweston.h b/include/libweston/libweston.h index 2e4f56a0..494ae55b 100644 --- a/include/libweston/libweston.h +++ b/include/libweston/libweston.h @@ -2042,6 +2042,7 @@ enum weston_compositor_backend { WESTON_BACKEND_DRM, WESTON_BACKEND_HEADLESS, WESTON_BACKEND_RDP, + WESTON_BACKEND_VNC, WESTON_BACKEND_WAYLAND, WESTON_BACKEND_X11, }; diff --git a/include/libweston/meson.build b/include/libweston/meson.build index 25451c14..43adfc13 100644 --- a/include/libweston/meson.build +++ b/include/libweston/meson.build @@ -15,6 +15,7 @@ install_headers( backend_drm_h = files('backend-drm.h') backend_headless_h = files('backend-headless.h') backend_rdp_h = files('backend-rdp.h') +backend_vnc_h = files('backend-vnc.h') backend_wayland_h = files('backend-wayland.h') backend_x11_h = files('backend-x11.h') diff --git a/libweston/backend-vnc/meson.build b/libweston/backend-vnc/meson.build new file mode 100644 index 00000000..4dfe0229 --- /dev/null +++ b/libweston/backend-vnc/meson.build @@ -0,0 +1,32 @@ +if not get_option('backend-vnc') + subdir_done() +endif + +config_h.set('BUILD_VNC_COMPOSITOR', '1') +dep_neatvnc = dependency('neatvnc', version: ['>= 0.5.0', '< 0.6.0'], required: false) +if not dep_neatvnc.found() + error('VNC backend requires neatvnc which was not found. Or, you can use \'-Dbackend-vnc=false\'.') +endif + +dep_aml = dependency('aml', version: ['>= 0.1.0', '< 0.3.0'], required: false) +if not dep_aml.found() + error('VNC backend requires libaml which was not found. Or, you can use \'-Dbackend-vnc=false\'.') +endif + +deps_vnc = [ + dep_libweston_private, + dep_neatvnc, + dep_aml, + dep_libdrm_headers, +] +plugin_vnc = shared_library( + 'vnc-backend', + [ 'vnc.c' ], + include_directories: common_inc, + dependencies: deps_vnc, + name_prefix: '', + install: true, + install_dir: dir_module_libweston +) +env_modmap += 'vnc-backend.so=@0@;'.format(plugin_vnc.full_path()) +install_headers(backend_vnc_h, subdir: dir_include_libweston_install) diff --git a/libweston/backend-vnc/vnc.c b/libweston/backend-vnc/vnc.c new file mode 100644 index 00000000..8cfad2ab --- /dev/null +++ b/libweston/backend-vnc/vnc.c @@ -0,0 +1,1032 @@ +/* + * Copyright © 2019-2020 Stefan Agner + * Copyright © 2021-2022 Pengutronix, Philipp Zabel + * Copyright © 2022 Pengutronix, Rouven Czerwinski + * based on backend-rdp: + * Copyright © 2013 Hardening + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "shared/helpers.h" +#include "shared/xalloc.h" +#include "shared/timespec-util.h" +#include +#include +#include "pixel-formats.h" +#include "pixman-renderer.h" + +#define DEFAULT_AXIS_STEP_DISTANCE 10 + +struct vnc_output; + +struct vnc_backend { + struct weston_backend base; + struct weston_compositor *compositor; + struct vnc_output *output; + + struct xkb_rule_names xkb_rule_name; + struct xkb_keymap *xkb_keymap; + + struct aml *aml; + struct wl_event_source *aml_event; + struct nvnc *server; + int vnc_monitor_refresh_rate; +}; + +struct vnc_output { + struct weston_output base; + struct wl_event_source *finish_frame_timer; + struct nvnc_display *display; + + struct nvnc_fb_pool *fb_pool; + + struct wl_list peers; + struct wl_list fb_side_data_list; +}; + +struct vnc_peer { + struct vnc_backend *backend; + struct weston_seat *seat; + struct nvnc_client *client; + + enum nvnc_button_mask last_button_mask; + struct wl_list link; +}; + +struct vnc_head { + struct weston_head base; +}; + +struct fb_side_data { + pixman_image_t *pixman_image; + pixman_region32_t damage; + struct wl_list link; +}; + +static inline struct vnc_backend * +to_vnc_backend(struct weston_compositor *base) +{ + return container_of(base->backend, struct vnc_backend, base); +} + +static void +vnc_output_destroy(struct weston_output *base); + +static inline struct vnc_output * +to_vnc_output(struct weston_output *base) +{ + if (base->destroy != vnc_output_destroy) + return NULL; + return container_of(base, struct vnc_output, base); +} + +static void +vnc_head_destroy(struct weston_head *base); + +static inline struct vnc_head * +to_vnc_head(struct weston_head *base) +{ + if (base->backend_id != vnc_head_destroy) + return NULL; + return container_of(base, struct vnc_head, base); +} + +struct vnc_keysym_to_keycode { + const uint32_t keysym; + const uint32_t code; + const bool shift; +}; + +static const +struct vnc_keysym_to_keycode key_translation[] = { + {XKB_KEY_KP_Enter, 0x60, false }, + {XKB_KEY_Return, 0x1c, false }, + {XKB_KEY_space, 0x39, false }, + {XKB_KEY_BackSpace, 0xe, false }, + {XKB_KEY_Tab, 0xf, false }, + {XKB_KEY_Escape, 0x1, false }, + {XKB_KEY_Shift_L, 0x2a, false }, + {XKB_KEY_Shift_R, 0x36, false }, + {XKB_KEY_Control_L, 0x1d, false }, + {XKB_KEY_Control_R, 0x9d, false }, + {XKB_KEY_Alt_L, 0x38, false }, + {XKB_KEY_Alt_R, 0x64, false }, + {XKB_KEY_Meta_L, 0x38, false }, + {XKB_KEY_Meta_R, 0x64, false }, + {XKB_KEY_Super_L, 0x7d, false }, + {XKB_KEY_Print, 0x63, false }, + {XKB_KEY_Pause, 0x77, false }, + {XKB_KEY_Caps_Lock, 0x3a, false }, + {XKB_KEY_Scroll_Lock, 0x46, false }, + {XKB_KEY_A, 0x1e, true }, + {XKB_KEY_a, 0x1e, false }, + {XKB_KEY_B, 0x30, true }, + {XKB_KEY_b, 0x30, false }, + {XKB_KEY_C, 0x2e, true }, + {XKB_KEY_c, 0x2e, false }, + {XKB_KEY_D, 0x20, true }, + {XKB_KEY_d, 0x20, false }, + {XKB_KEY_E, 0x12, true }, + {XKB_KEY_e, 0x12, false }, + {XKB_KEY_F, 0x21, true }, + {XKB_KEY_f, 0x21, false }, + {XKB_KEY_G, 0x22, true }, + {XKB_KEY_g, 0x22, false }, + {XKB_KEY_H, 0x23, true }, + {XKB_KEY_h, 0x23, false }, + {XKB_KEY_I, 0x17, true }, + {XKB_KEY_i, 0x17, false }, + {XKB_KEY_J, 0x24, true }, + {XKB_KEY_j, 0x24, false }, + {XKB_KEY_K, 0x25, true }, + {XKB_KEY_k, 0x25, false }, + {XKB_KEY_L, 0x26, true }, + {XKB_KEY_l, 0x26, false }, + {XKB_KEY_M, 0x32, true }, + {XKB_KEY_m, 0x32, false }, + {XKB_KEY_N, 0x31, true }, + {XKB_KEY_n, 0x31, false }, + {XKB_KEY_O, 0x18, true }, + {XKB_KEY_o, 0x18, false }, + {XKB_KEY_P, 0x19, true }, + {XKB_KEY_p, 0x19, false }, + {XKB_KEY_Q, 0x10, true }, + {XKB_KEY_q, 0x10, false }, + {XKB_KEY_R, 0x13, true }, + {XKB_KEY_r, 0x13, false }, + {XKB_KEY_S, 0x1f, true }, + {XKB_KEY_s, 0x1f, false }, + {XKB_KEY_T, 0x14, true }, + {XKB_KEY_t, 0x14, false }, + {XKB_KEY_U, 0x16, true }, + {XKB_KEY_u, 0x16, false }, + {XKB_KEY_V, 0x2f, true }, + {XKB_KEY_v, 0x2f, false }, + {XKB_KEY_W, 0x11, true }, + {XKB_KEY_w, 0x11, false }, + {XKB_KEY_X, 0x2d, true }, + {XKB_KEY_x, 0x2d, false }, + {XKB_KEY_Y, 0x15, true }, + {XKB_KEY_y, 0x15, false }, + {XKB_KEY_Z, 0x2c, true }, + {XKB_KEY_z, 0x2c, false }, + {XKB_KEY_grave, 0x29, false }, + {XKB_KEY_asciitilde, 0x29, true }, + {XKB_KEY_1, 0x02, false }, + {XKB_KEY_exclam, 0x02, true }, + {XKB_KEY_2, 0x03, false }, + {XKB_KEY_at, 0x03, true }, + {XKB_KEY_3, 0x04, false }, + {XKB_KEY_numbersign, 0x04, true }, + {XKB_KEY_4, 0x05, false }, + {XKB_KEY_dollar, 0x05, true }, + {XKB_KEY_5, 0x06, false }, + {XKB_KEY_percent, 0x06, true }, + {XKB_KEY_6, 0x07, false }, + {XKB_KEY_asciicircum, 0x07, true }, + {XKB_KEY_7, 0x08, false }, + {XKB_KEY_ampersand, 0x08, true }, + {XKB_KEY_8, 0x09, false }, + {XKB_KEY_asterisk, 0x09, true }, + {XKB_KEY_9, 0x0a, false }, + {XKB_KEY_parenleft, 0x0a, true }, + {XKB_KEY_0, 0x0b, false }, + {XKB_KEY_parenright, 0x0b, true }, + {XKB_KEY_minus, 0x0c, false, }, + {XKB_KEY_underscore, 0x0c, true }, + {XKB_KEY_equal, 0x0d, false }, + {XKB_KEY_plus, 0x0d, true }, + {XKB_KEY_bracketleft, 0x1a, false }, + {XKB_KEY_braceleft, 0x1a, true }, + {XKB_KEY_bracketright, 0x1b, false }, + {XKB_KEY_braceright, 0x1b, true }, + {XKB_KEY_semicolon, 0x27, false }, + {XKB_KEY_colon, 0x27, true }, + {XKB_KEY_apostrophe, 0x28, false }, + {XKB_KEY_quotedbl, 0x28, true }, + {XKB_KEY_backslash, 0x2b, false }, + {XKB_KEY_bar, 0x2b, true }, + {XKB_KEY_comma, 0x33, false }, + {XKB_KEY_less, 0x33, true }, + {XKB_KEY_period, 0x34, false }, + {XKB_KEY_greater, 0x34, true }, + {XKB_KEY_slash, 0x35, false }, + {XKB_KEY_question, 0x35, true }, + {XKB_KEY_F1, 0x3b, false }, + {XKB_KEY_F2, 0x3c, false }, + {XKB_KEY_F3, 0x3d, false }, + {XKB_KEY_F4, 0x3e, false }, + {XKB_KEY_F5, 0x3f, false }, + {XKB_KEY_F6, 0x40, false }, + {XKB_KEY_F7, 0x41, false }, + {XKB_KEY_F8, 0x42, false }, + {XKB_KEY_F9, 0x43, false }, + {XKB_KEY_F10, 0x44, false }, + {XKB_KEY_F11, 0x57, false }, + {XKB_KEY_F12, 0x58, false }, + {XKB_KEY_Home, 0x66, false }, + {XKB_KEY_Up, 0x67, false }, + {XKB_KEY_Prior, 0x68, false }, + {XKB_KEY_Left, 0x69, false }, + {XKB_KEY_Right, 0x6a, false }, + {XKB_KEY_End, 0x6b, false }, + {XKB_KEY_Down, 0x6c, false }, + {XKB_KEY_Next, 0x6d, false }, + { }, +}; + +static void +vnc_handle_key_event(struct nvnc_client *client, uint32_t keysym, + bool is_pressed) +{ + struct vnc_peer *peer = nvnc_get_userdata(client); + uint32_t key = 0; + bool needs_shift = false; + enum weston_key_state_update state_update; + enum wl_keyboard_key_state state; + struct timespec time; + int i; + + weston_compositor_get_time(&time); + + if (is_pressed) + state = WL_KEYBOARD_KEY_STATE_PRESSED; + else + state = WL_KEYBOARD_KEY_STATE_RELEASED; + + /* Generally ignore shift state as per RFC6143 Section 7.5.4 */ + if (keysym == XKB_KEY_Shift_L || keysym == XKB_KEY_Shift_R) + return; + + /* Allow selected modifiers */ + if (keysym == XKB_KEY_Control_L || keysym == XKB_KEY_Control_R || + keysym == XKB_KEY_Alt_L || keysym == XKB_KEY_Alt_R) + state_update = STATE_UPDATE_AUTOMATIC; + else + state_update = STATE_UPDATE_NONE; + + for (i = 0; key_translation[i].keysym; i++) { + if (key_translation[i].keysym == keysym) { + key = key_translation[i].code; + needs_shift = key_translation[i].shift; + break; + } + } + + if (!key) { + weston_log("Key not found: keysym %08x, translated %08x\n", + keysym, key); + return; + } + + /* emulate lshift press */ + if (needs_shift) + notify_key(peer->seat, &time, KEY_LEFTSHIFT, + WL_KEYBOARD_KEY_STATE_PRESSED, + STATE_UPDATE_AUTOMATIC); + + /* send detected key code */ + notify_key(peer->seat, &time, key, state, state_update); + + /* emulate lshift release */ + if (needs_shift) + notify_key(peer->seat, &time, KEY_LEFTSHIFT, + WL_KEYBOARD_KEY_STATE_RELEASED, + STATE_UPDATE_AUTOMATIC); +} + +static void +vnc_pointer_event(struct nvnc_client *client, uint16_t x, uint16_t y, + enum nvnc_button_mask button_mask) +{ + struct vnc_peer *peer = nvnc_get_userdata(client); + struct vnc_output *output = peer->backend->output; + struct timespec time; + enum nvnc_button_mask changed_button_mask; + + weston_compositor_get_time(&time); + + if (x < output->base.width && y < output->base.height) { + double global_x, global_y; + + weston_output_transform_coordinate(&output->base, x, y, + &global_x, &global_y); + notify_motion_absolute(peer->seat, &time, global_x, global_y); + } + + changed_button_mask = peer->last_button_mask ^ button_mask; + + if (changed_button_mask & NVNC_BUTTON_LEFT) + notify_button(peer->seat, &time, BTN_LEFT, + (button_mask & NVNC_BUTTON_LEFT) ? + WL_POINTER_BUTTON_STATE_PRESSED : + WL_POINTER_BUTTON_STATE_RELEASED); + + if (changed_button_mask & NVNC_BUTTON_MIDDLE) + notify_button(peer->seat, &time, BTN_MIDDLE, + (button_mask & NVNC_BUTTON_MIDDLE) ? + WL_POINTER_BUTTON_STATE_PRESSED : + WL_POINTER_BUTTON_STATE_RELEASED); + + if (changed_button_mask & NVNC_BUTTON_RIGHT) + notify_button(peer->seat, &time, BTN_RIGHT, + (button_mask & NVNC_BUTTON_RIGHT) ? + WL_POINTER_BUTTON_STATE_PRESSED : + WL_POINTER_BUTTON_STATE_RELEASED); + + if ((button_mask & NVNC_SCROLL_UP) || + (button_mask & NVNC_SCROLL_DOWN)) { + struct weston_pointer_axis_event weston_event; + + weston_event.axis = WL_POINTER_AXIS_VERTICAL_SCROLL; + + /* DEFAULT_AXIS_STEP_DISTANCE is stolen from compositor-x11.c */ + if (button_mask & NVNC_SCROLL_UP) + weston_event.value = -DEFAULT_AXIS_STEP_DISTANCE; + if (button_mask & NVNC_SCROLL_DOWN) + weston_event.value = DEFAULT_AXIS_STEP_DISTANCE; + weston_event.has_discrete = false; + + notify_axis(peer->seat, &time, &weston_event); + } + + peer->last_button_mask = button_mask; + + notify_pointer_frame(peer->seat); +} + +static void +vnc_client_cleanup(struct nvnc_client *client) +{ + struct vnc_peer *peer = nvnc_get_userdata(client); + + wl_list_remove(&peer->link); + weston_seat_release_keyboard(peer->seat); + weston_seat_release_pointer(peer->seat); + weston_seat_release(peer->seat); + free(peer); + weston_log("VNC Client disconnected\n"); +} + +static void +fb_side_data_destroy(void *userdata) +{ + struct fb_side_data *fb_side_data = userdata; + + wl_list_remove(&fb_side_data->link); + pixman_region32_fini(&fb_side_data->damage); + pixman_image_unref(fb_side_data->pixman_image); + free(fb_side_data); +} + + +/* + * Convert damage rectangles from 32-bit global coordinates to 16-bit local + * coordinates. The output transformation has to be a pure translation. + */ +static void +vnc_convert_damage(struct pixman_region16 *dst, struct pixman_region32 *src, + int x, int y) +{ + struct pixman_box32 *src_rects; + struct pixman_box16 *dest_rects; + int n_rects = 0; + int i; + + src_rects = pixman_region32_rectangles(src, &n_rects); + if (!n_rects) + return; + + dest_rects = xcalloc(n_rects, sizeof(*dest_rects)); + + for (i = 0; i < n_rects; i++) { + dest_rects[i].x1 = src_rects[i].x1 - x; + dest_rects[i].y1 = src_rects[i].y1 - y; + dest_rects[i].x2 = src_rects[i].x2 - x; + dest_rects[i].y2 = src_rects[i].y2 - y; + } + + pixman_region_init_rects(dst, dest_rects, n_rects); + free(dest_rects); +} + +static void +vnc_update_buffer(struct nvnc_display *display, struct pixman_region32 *damage) +{ + struct nvnc *server = nvnc_display_get_server(display); + struct vnc_backend *backend = nvnc_get_userdata(server); + struct vnc_output *output = backend->output; + struct weston_compositor *ec = output->base.compositor; + struct fb_side_data *fb_side_data; + pixman_region16_t local_damage; + struct nvnc_fb *fb; + + fb = nvnc_fb_pool_acquire(output->fb_pool); + assert(fb); + + fb_side_data = nvnc_get_userdata(fb); + if (!fb_side_data) { + const struct pixel_format_info *pfmt; + + fb_side_data = xzalloc(sizeof(*fb_side_data)); + + pfmt = pixel_format_get_info(DRM_FORMAT_XRGB8888); + fb_side_data->pixman_image = + pixman_image_create_bits(pfmt->pixman_format, + output->base.width, + output->base.height, + nvnc_fb_get_addr(fb), + output->base.width * 4); + + /* This is a new buffer, so the whole surface is damaged. */ + pixman_region32_copy(&fb_side_data->damage, + &output->base.region); + + nvnc_set_userdata(fb, fb_side_data, fb_side_data_destroy); + wl_list_insert(&output->fb_side_data_list, &fb_side_data->link); + } + + pixman_renderer_output_set_buffer(&output->base, + fb_side_data->pixman_image); + + ec->renderer->repaint_output(&output->base, &fb_side_data->damage); + + /* Convert to local coordinates before clearing accumulated damage */ + pixman_region_init(&local_damage); + vnc_convert_damage(&local_damage, &fb_side_data->damage, + output->base.x, output->base.y); + + /* Clear accumulated damage after repaint */ + pixman_region32_clear(&fb_side_data->damage); + + nvnc_display_feed_buffer(output->display, fb, &local_damage); + nvnc_fb_unref(fb); + pixman_region_fini(&local_damage); +} + +static void +vnc_new_client(struct nvnc_client *client) +{ + struct nvnc *server = nvnc_client_get_server(client); + struct vnc_backend *backend = nvnc_get_userdata(server); + struct vnc_output *output = backend->output; + struct vnc_peer *peer; + const char *seat_name = "VNC Client"; + + weston_log("New VNC client connected\n"); + + peer = xzalloc(sizeof(*peer)); + peer->client = client; + peer->backend = backend; + peer->seat = zalloc(sizeof(*peer->seat)); + + if (!peer->seat) { + weston_log("unable to create a weston_seat\n"); + return; + } + weston_seat_init(peer->seat, backend->compositor, seat_name); + weston_seat_init_pointer(peer->seat); + weston_seat_init_keyboard(peer->seat, backend->xkb_keymap); + + wl_list_insert(&output->peers, &peer->link); + + nvnc_set_userdata(client, peer, NULL); + nvnc_set_client_cleanup_fn(client, vnc_client_cleanup); + + /* + * Make up for repaints that were skipped when no clients were + * connected. + */ + weston_output_schedule_repaint(&output->base); +} + + +static int +finish_frame_handler(void *data) +{ + struct vnc_output *output = data; + int refresh_nsec = millihz_to_nsec(output->base.current_mode->refresh); + struct timespec now, ts; + int delta; + + /* The timer only has msec precision, but if we approximately hit our + * target, report an exact time stamp by adding to the previous frame + * time. + */ + timespec_add_nsec(&ts, &output->base.frame_time, refresh_nsec); + + /* If we are more than 1.5 ms late, report the current time instead. */ + weston_compositor_read_presentation_clock(output->base.compositor, &now); + delta = (int)timespec_sub_to_nsec(&now, &ts); + if (delta > 1500000) + ts = now; + + weston_output_finish_frame(&output->base, &ts, 0); + + return 1; +} + +static int +vnc_output_enable(struct weston_output *base) +{ + struct vnc_output *output = to_vnc_output(base); + struct vnc_backend *backend; + struct wl_event_loop *loop; + const struct pixman_renderer_output_options options = { + .use_shadow = true, + .fb_size = { + .width = output->base.width, + .height = output->base.height, + }, + }; + + assert(output); + + backend = to_vnc_backend(base->compositor); + backend->output = output; + + if (pixman_renderer_output_create(&output->base, &options) < 0) + return -1; + + loop = wl_display_get_event_loop(backend->compositor->wl_display); + output->finish_frame_timer = wl_event_loop_add_timer(loop, + finish_frame_handler, + output); + + output->fb_pool = nvnc_fb_pool_new(output->base.width, + output->base.height, + DRM_FORMAT_XRGB8888, + output->base.width); + + output->display = nvnc_display_new(0, 0); + + wl_list_init(&output->fb_side_data_list); + + nvnc_add_display(backend->server, output->display); + + /* + * Neat VNC warns when a client connects before a display buffer has + * been set. Repaint once to create an initial buffer. + */ + vnc_update_buffer(output->display, &output->base.region); + + return 0; +} + +static int +vnc_output_disable(struct weston_output *base) +{ + struct vnc_output *output = to_vnc_output(base); + struct vnc_backend *backend; + + assert(output); + + backend = to_vnc_backend(base->compositor); + + if (!output->base.enabled) + return 0; + + pixman_renderer_output_destroy(&output->base); + + nvnc_display_unref(output->display); + nvnc_fb_pool_unref(output->fb_pool); + + wl_event_source_remove(output->finish_frame_timer); + backend->output = NULL; + + return 0; +} + +static void +vnc_output_destroy(struct weston_output *base) +{ + struct vnc_output *output = to_vnc_output(base); + + /* Can only be called on outputs created by vnc_create_output() */ + assert(output); + + vnc_output_disable(&output->base); + weston_output_release(&output->base); + + free(output); +} + +static struct weston_output * +vnc_create_output(struct weston_compositor *compositor, const char *name) +{ + struct vnc_output *output; + + output = zalloc(sizeof *output); + if (output == NULL) + return NULL; + + weston_output_init(&output->base, compositor, name); + + output->base.destroy = vnc_output_destroy; + output->base.disable = vnc_output_disable; + output->base.enable = vnc_output_enable; + output->base.attach_head = NULL; + + weston_compositor_add_pending_output(&output->base, compositor); + + return &output->base; +} + +static void +vnc_destroy(struct weston_compositor *ec) +{ + struct weston_head *base, *next; + struct vnc_backend *backend = to_vnc_backend(ec); + + nvnc_close(backend->server); + + weston_compositor_shutdown(ec); + + wl_event_source_remove(backend->aml_event); + + aml_unref(backend->aml); + + wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) + vnc_head_destroy(base); + + xkb_keymap_unref(backend->xkb_keymap); + + free(backend); +} + +static int +vnc_head_create(struct weston_compositor *compositor, const char *name) +{ + struct vnc_head *head; + + head = zalloc(sizeof *head); + if (!head) + return -1; + + weston_head_init(&head->base, name); + + head->base.backend_id = vnc_head_destroy; + + weston_head_set_connection_status(&head->base, true); + weston_compositor_add_head(compositor, &head->base); + + return 0; +} + +static void +vnc_head_destroy(struct weston_head *base) +{ + struct vnc_head *head = to_vnc_head(base); + + if (!head) + return; + + weston_head_release(&head->base); + free(head); +} + +static int +vnc_output_start_repaint_loop(struct weston_output *output) +{ + struct timespec ts; + + weston_compositor_read_presentation_clock(output->compositor, &ts); + weston_output_finish_frame(output, &ts, WP_PRESENTATION_FEEDBACK_INVALID); + + return 0; +} + +static int +vnc_output_repaint(struct weston_output *base, pixman_region32_t *damage) +{ + struct vnc_output *output = to_vnc_output(base); + struct weston_compositor *ec = output->base.compositor; + struct vnc_backend *backend = to_vnc_backend(ec); + struct timespec now, target; + int refresh_nsec = millihz_to_nsec(output->base.current_mode->refresh); + int refresh_msec = refresh_nsec / 1000000; + int next_frame_delta; + + assert(output); + + if (pixman_region32_not_empty(damage)) { + struct fb_side_data *fb_side_data; + + /* Accumulate damage in all buffers */ + wl_list_for_each(fb_side_data, &output->fb_side_data_list, link) + pixman_region32_union(&fb_side_data->damage, + &fb_side_data->damage, damage); + + /* Only repaint when a client is connected */ + if (!wl_list_empty(&output->peers)) + vnc_update_buffer(output->display, damage); + + pixman_region32_subtract(&ec->primary_plane.damage, + &ec->primary_plane.damage, damage); + } + + /* + * Make sure damage of this (or previous) damage is handled + * + * This will usually invoke the render callback where the (pixman) + * renderer gets invoked + */ + aml_dispatch(backend->aml); + + weston_compositor_read_presentation_clock(ec, &now); + timespec_add_nsec(&target, &output->base.frame_time, refresh_nsec); + + next_frame_delta = (int)timespec_sub_to_msec(&target, &now); + if (next_frame_delta < 1) + next_frame_delta = 1; + if (next_frame_delta > refresh_msec) + next_frame_delta = refresh_msec; + + wl_event_source_timer_update(output->finish_frame_timer, + next_frame_delta); + + return 0; +} + +static struct weston_mode * +vnc_insert_new_mode(struct weston_output *output, int width, int height, + int rate) +{ + struct weston_mode *mode; + + mode = zalloc(sizeof *mode); + if (!mode) + return NULL; + mode->width = width; + mode->height = height; + mode->refresh = rate; + wl_list_insert(&output->mode_list, &mode->link); + + return mode; +} + +static struct weston_mode * +vnc_ensure_matching_mode(struct weston_output *output, + struct weston_mode *target) +{ + struct vnc_backend *backend = to_vnc_backend(output->compositor); + struct weston_mode *local; + + wl_list_for_each(local, &output->mode_list, link) { + if ((local->width == target->width) && + (local->height == target->height)) + return local; + } + + return vnc_insert_new_mode(output, target->width, target->height, + backend->vnc_monitor_refresh_rate); +} + +static int +vnc_switch_mode(struct weston_output *base, struct weston_mode *target_mode) +{ + struct vnc_output *output = to_vnc_output(base); + struct weston_mode *local_mode; + struct weston_size fb_size; + + assert(output); + + local_mode = vnc_ensure_matching_mode(base, target_mode); + if (!local_mode) { + weston_log("mode %dx%d not available\n", + target_mode->width, target_mode->height); + return -ENOENT; + } + + if (local_mode == base->current_mode) + return 0; + + base->current_mode->flags &= ~WL_OUTPUT_MODE_CURRENT; + + base->current_mode = base->native_mode = local_mode; + base->current_mode->flags |= WL_OUTPUT_MODE_CURRENT; + + fb_size.width = target_mode->width; + fb_size.height = target_mode->height; + + weston_renderer_resize_output(base, &fb_size, NULL); + + nvnc_fb_pool_unref(output->fb_pool); + + output->fb_pool = nvnc_fb_pool_new(target_mode->width, + target_mode->height, + DRM_FORMAT_XRGB8888, + target_mode->width * 4); + + return 0; +} + +static int +vnc_output_set_size(struct weston_output *base, int width, int height) +{ + struct vnc_output *output = to_vnc_output(base); + struct vnc_backend *backend = to_vnc_backend(base->compositor); + struct weston_head *head; + struct weston_mode *current_mode; + struct weston_mode init_mode; + + /* We can only be called once. */ + assert(!output->base.current_mode); + + wl_list_for_each(head, &output->base.head_list, output_link) { + weston_head_set_monitor_strings(head, "weston", "vnc", NULL); + + weston_head_set_physical_size(head, 0, 0); + } + + wl_list_init(&output->peers); + + init_mode.flags = WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; + init_mode.width = width; + init_mode.height = height; + init_mode.refresh = backend->vnc_monitor_refresh_rate; + + current_mode = vnc_ensure_matching_mode(&output->base, &init_mode); + if (!current_mode) + return -1; + + output->base.current_mode = output->base.native_mode = current_mode; + + output->base.start_repaint_loop = vnc_output_start_repaint_loop; + output->base.repaint = vnc_output_repaint; + output->base.assign_planes = NULL; + output->base.set_backlight = NULL; + output->base.set_dpms = NULL; + output->base.switch_mode = vnc_switch_mode; + + return 0; +} + +static const struct weston_vnc_output_api api = { + vnc_output_set_size, +}; + +static int +vnc_aml_dispatch(int fd, uint32_t mask, void *data) +{ + struct aml *aml = data; + + aml_poll(aml, 0); + aml_dispatch(aml); + + return 0; +} + +static struct vnc_backend * +vnc_backend_create(struct weston_compositor *compositor, + struct weston_vnc_backend_config *config) +{ + struct vnc_backend *backend; + struct wl_event_loop *loop; + struct weston_head *base, *next; + int ret; + int fd; + + backend = zalloc(sizeof *backend); + if (backend == NULL) + return NULL; + + backend->compositor = compositor; + backend->base.destroy = vnc_destroy; + backend->base.create_output = vnc_create_output; + backend->vnc_monitor_refresh_rate = config->refresh_rate * 1000; + + compositor->backend = &backend->base; + + if (weston_compositor_set_presentation_clock_software(compositor) < 0) + goto err_compositor; + + if (pixman_renderer_init(compositor) < 0) + goto err_compositor; + + if (vnc_head_create(compositor, "vnc") < 0) + goto err_compositor; + + compositor->capabilities |= WESTON_CAP_ARBITRARY_MODES; + + backend->xkb_rule_name.rules = strdup("evdev"); + backend->xkb_rule_name.model = strdup("pc105"); + backend->xkb_rule_name.layout = strdup("us"); + + backend->xkb_keymap = xkb_keymap_new_from_names( + backend->compositor->xkb_context, + &backend->xkb_rule_name, 0); + + loop = wl_display_get_event_loop(backend->compositor->wl_display); + + backend->aml = aml_new(); + if (!backend->aml) + goto err_output; + aml_set_default(backend->aml); + + fd = aml_get_fd(backend->aml); + + backend->aml_event = wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE, + vnc_aml_dispatch, + backend->aml); + + backend->server = nvnc_open(config->bind_address, config->port); + if (!backend->server) + goto err_output; + + nvnc_set_new_client_fn(backend->server, vnc_new_client); + nvnc_set_pointer_fn(backend->server, vnc_pointer_event); + nvnc_set_key_fn(backend->server, vnc_handle_key_event); + nvnc_set_userdata(backend->server, backend, NULL); + nvnc_set_name(backend->server, "Weston VNC backend"); + + ret = weston_plugin_api_register(compositor, WESTON_VNC_OUTPUT_API_NAME, + &api, sizeof(api)); + if (ret < 0) { + weston_log("Failed to register output API.\n"); + goto err_output; + } + + return backend; + +err_output: + if (backend->output) + weston_output_release(&backend->output->base); + wl_list_for_each_safe(base, next, &compositor->head_list, compositor_link) + vnc_head_destroy(base); +err_compositor: + weston_compositor_shutdown(compositor); + free(backend); + return NULL; +} + +static void +config_init_to_defaults(struct weston_vnc_backend_config *config) +{ + config->bind_address = NULL; + config->port = 5900; + config->refresh_rate = VNC_DEFAULT_FREQ; +} + +WL_EXPORT int +weston_backend_init(struct weston_compositor *compositor, + struct weston_backend_config *config_base) +{ + struct vnc_backend *backend; + struct weston_vnc_backend_config config = {{ 0, }}; + + weston_log("Initializing VNC backend\n"); + + if (config_base == NULL || + config_base->struct_version != WESTON_VNC_BACKEND_CONFIG_VERSION || + config_base->struct_size > sizeof(struct weston_vnc_backend_config)) { + weston_log("VNC backend config structure is invalid\n"); + return -1; + } + + config_init_to_defaults(&config); + memcpy(&config, config_base, config_base->struct_size); + + backend = vnc_backend_create(compositor, &config); + if (backend == NULL) + return -1; + return 0; +} diff --git a/libweston/compositor.c b/libweston/compositor.c index 548a635b..858c514e 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -8687,6 +8687,7 @@ static const char * const backend_map[] = { [WESTON_BACKEND_DRM] = "drm-backend.so", [WESTON_BACKEND_HEADLESS] = "headless-backend.so", [WESTON_BACKEND_RDP] = "rdp-backend.so", + [WESTON_BACKEND_VNC] = "vnc-backend.so", [WESTON_BACKEND_WAYLAND] = "wayland-backend.so", [WESTON_BACKEND_X11] = "x11-backend.so", }; diff --git a/libweston/meson.build b/libweston/meson.build index ef984d54..4cda7a70 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -231,5 +231,6 @@ subdir('renderer-gl') subdir('backend-drm') subdir('backend-headless') subdir('backend-rdp') +subdir('backend-vnc') subdir('backend-wayland') subdir('backend-x11') diff --git a/meson_options.txt b/meson_options.txt index d0d1f6cd..4eb09976 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -32,6 +32,12 @@ option( value: true, description: 'Compositor: RDP screen-sharing support' ) +option( + 'backend-vnc', + type: 'boolean', + value: true, + description: 'Weston backend: VNC remote screensharing' +) option( 'backend-wayland', type: 'boolean',