/* * 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 #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 wl_listener output_move_listener; 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 vnc_backend *backend; 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 void vnc_destroy(struct weston_backend *backend); static inline struct vnc_head * to_vnc_head(struct weston_head *base) { if (base->backend->destroy != vnc_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_handle_key_code_event(struct nvnc_client *client, uint32_t key, bool is_pressed) { struct vnc_peer *peer = nvnc_get_userdata(client); enum wl_keyboard_key_state state; struct timespec time; weston_compositor_get_time(&time); if (is_pressed) state = WL_KEYBOARD_KEY_STATE_PRESSED; else state = WL_KEYBOARD_KEY_STATE_RELEASED; notify_key(peer->seat, &time, key, state, 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 bool vnc_handle_auth(const char *username, const char *password, void *userdata) { struct passwd *pw = getpwnam(username); if (!pw || pw->pw_uid != getuid()) { weston_log("VNC: wrong user '%s'\n", username); return false; } return weston_authenticate_user(username, password); } 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 = xzalloc(sizeof(*peer->seat)); 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 = output->backend; 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 = output->backend; 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_backend *backend, const char *name) { struct vnc_backend *b = container_of(backend, struct vnc_backend, base); struct vnc_output *output; output = zalloc(sizeof *output); if (output == NULL) return NULL; weston_output_init(&output->base, b->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; output->backend = b; weston_compositor_add_pending_output(&output->base, b->compositor); return &output->base; } static void vnc_destroy(struct weston_backend *base) { struct vnc_backend *backend = container_of(base, struct vnc_backend, base); struct weston_compositor *ec = backend->compositor; struct weston_head *head, *next; wl_list_remove(&backend->output_move_listener.link); nvnc_close(backend->server); weston_compositor_shutdown(ec); wl_event_source_remove(backend->aml_event); aml_unref(backend->aml); wl_list_for_each_safe(head, next, &ec->head_list, compositor_link) vnc_head_destroy(head); xkb_keymap_unref(backend->xkb_keymap); free(backend); } static void vnc_head_create(struct weston_compositor *compositor, const char *name) { struct vnc_backend *backend = to_vnc_backend(compositor); struct vnc_head *head; head = xzalloc(sizeof *head); weston_head_init(&head->base, name); weston_head_set_monitor_strings(&head->base, "weston", "vnc", NULL); weston_head_set_physical_size(&head->base, 0, 0); head->base.backend = &backend->base; weston_head_set_connection_status(&head->base, true); weston_compositor_add_head(compositor, &head->base); } 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 = output->backend; 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 = xzalloc(sizeof *mode); 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 vnc_output *output, struct weston_mode *target) { struct vnc_backend *backend = output->backend; struct weston_mode *local; wl_list_for_each(local, &output->base.mode_list, link) { if ((local->width == target->width) && (local->height == target->height)) return local; } return vnc_insert_new_mode(&output->base, 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(output, target_mode); 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_resize(output->fb_pool, target_mode->width, target_mode->height, DRM_FORMAT_XRGB8888, target_mode->width); 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 = output->backend; struct weston_mode *current_mode; struct weston_mode init_mode; /* We can only be called once. */ assert(!output->base.current_mode); wl_list_init(&output->peers); init_mode.width = width; init_mode.height = height; init_mode.refresh = backend->vnc_monitor_refresh_rate; current_mode = vnc_ensure_matching_mode(output, &init_mode); current_mode->flags = WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; 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 void vnc_handle_output_move(struct wl_listener *listener, void *data) { struct weston_output *base = data; struct vnc_output *output = to_vnc_output(base); struct fb_side_data *fb_side_data; if (!output) return; /* Move accumulated damage with output */ wl_list_for_each(fb_side_data, &output->fb_side_data_list, link) pixman_region32_translate(&fb_side_data->damage, base->move_x, base->move_y); } 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; switch (config->renderer) { case WESTON_RENDERER_AUTO: case WESTON_RENDERER_PIXMAN: weston_log("Using Pixman renderer\n"); break; default: weston_log("Unsupported renderer requested\n"); goto err_compositor; } if (pixman_renderer_init(compositor) < 0) goto err_compositor; vnc_head_create(compositor, "vnc"); compositor->capabilities |= WESTON_CAP_ARBITRARY_MODES; backend->xkb_rule_name.rules = strdup(compositor->xkb_names.rules); backend->xkb_rule_name.model = strdup(compositor->xkb_names.model); backend->xkb_rule_name.layout = strdup(compositor->xkb_names.layout); 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_key_code_fn(backend->server, vnc_handle_key_code_event); nvnc_set_userdata(backend->server, backend, NULL); nvnc_set_name(backend->server, "Weston VNC backend"); if (!nvnc_has_auth()) { weston_log("Neat VNC built without TLS support\n"); goto err_output; } if (!config->server_cert && !config->server_key) { weston_log("The VNC backend requires a key and a certificate for TLS security" " (--vnc-tls-cert/--vnc-tls-key)\n"); goto err_output; } if (!config->server_cert) { weston_log("Missing TLS certificate (--vnc-tls-cert)\n"); goto err_output; } if (!config->server_key) { weston_log("Missing TLS key (--vnc-tls-key)\n"); goto err_output; } ret = nvnc_enable_auth(backend->server, config->server_key, config->server_cert, vnc_handle_auth, NULL); if (ret) { weston_log("Failed to enable TLS support\n"); goto err_output; } weston_log("TLS support activated\n"); backend->output_move_listener.notify = vnc_handle_output_move; wl_signal_add(&compositor->output_moved_signal, &backend->output_move_listener); 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: 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; }