weston/libweston/backend-vnc/vnc.c

1115 lines
30 KiB
C
Raw Normal View History

/*
* Copyright © 2019-2020 Stefan Agner <stefan@agner.ch>
* Copyright © 2021-2022 Pengutronix, Philipp Zabel <p.zabel@pengutronix.de>
* Copyright © 2022 Pengutronix, Rouven Czerwinski <r.czerwinski@pengutronix.de>
* based on backend-rdp:
* Copyright © 2013 Hardening <rdp.effort@gmail.com>
*
* 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 <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <linux/input.h>
#include <netinet/in.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <xkbcommon/xkbcommon-keysyms.h>
#include <xkbcommon/xkbcommon.h>
#define AML_UNSTABLE_API 1
#include <aml.h>
#include <neatvnc.h>
#include <drm_fourcc.h>
#include "shared/helpers.h"
#include "shared/xalloc.h"
#include "shared/timespec-util.h"
#include <libweston/libweston.h>
#include <libweston/backend-vnc.h>
#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 {
struct weston_renderer *renderer;
struct weston_renderbuffer *renderbuffer;
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);
weston_renderbuffer_unref(fb_side_data->renderbuffer);
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 pixman_renderer_interface *pixman;
const struct pixel_format_info *pfmt;
fb_side_data = xzalloc(sizeof(*fb_side_data));
pixman = ec->renderer->pixman;
pfmt = pixel_format_get_info(DRM_FORMAT_XRGB8888);
fb_side_data->renderer = ec->renderer;
fb_side_data->renderbuffer =
pixman->create_image_from_ptr(&output->base, pfmt,
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);
}
ec->renderer->repaint_output(&output->base, &fb_side_data->damage,
fb_side_data->renderbuffer);
/* 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 weston_renderer *renderer = base->compositor->renderer;
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,
},
.format = pixel_format_get_info(DRM_FORMAT_XRGB8888),
};
assert(output);
backend = output->backend;
backend->output = output;
if (renderer->pixman->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,
options.format->format,
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 weston_renderer *renderer = base->compositor->renderer;
struct vnc_output *output = to_vnc_output(base);
struct vnc_backend *backend;
assert(output);
backend = output->backend;
if (!output->base.enabled)
return 0;
nvnc_display_unref(output->display);
nvnc_fb_pool_unref(output->fb_pool);
renderer->pixman->output_destroy(&output->base);
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 (weston_compositor_init_renderer(compositor, WESTON_RENDERER_PIXMAN,
NULL) < 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;
}