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',