diff --git a/include/common.h b/include/common.h index 47db985..7c3dafc 100644 --- a/include/common.h +++ b/include/common.h @@ -89,6 +89,7 @@ struct nvnc_client { struct cut_text cut_text; bool is_qemu_key_ext_notified; struct encoder* encoder; + uint32_t cursor_seq; }; LIST_HEAD(nvnc_client_list, nvnc_client); @@ -107,6 +108,11 @@ struct nvnc { nvnc_client_fn new_client_fn; nvnc_cut_text_fn cut_text_fn; struct nvnc_display* display; + struct { + struct nvnc_fb* buffer; + uint32_t x_hotspot, y_hotspot; + } cursor; + uint32_t cursor_seq; #ifdef ENABLE_TLS gnutls_certificate_credentials_t tls_creds; diff --git a/include/neatvnc.h b/include/neatvnc.h index 991e2c1..3b6e99e 100644 --- a/include/neatvnc.h +++ b/include/neatvnc.h @@ -141,3 +141,6 @@ void nvnc_display_feed_buffer(struct nvnc_display*, struct nvnc_fb*, struct pixman_region16* damage); void nvnc_send_cut_text(struct nvnc*, const char* text, uint32_t len); + +void nvnc_set_cursor(struct nvnc*, struct nvnc_fb*, uint16_t x_hotspot, + uint16_t y_hotspot, struct pixman_region16* damage); diff --git a/meson.build b/meson.build index 05862ee..fbc478d 100644 --- a/meson.build +++ b/meson.build @@ -83,6 +83,7 @@ sources = [ 'src/damage-refinery.c', 'src/murmurhash.c', 'src/encoder.c', + 'src/cursor.c', ] dependencies = [ diff --git a/src/server.c b/src/server.c index 10d260c..a7d54c7 100644 --- a/src/server.c +++ b/src/server.c @@ -29,6 +29,7 @@ #include "encoder.h" #include "tight.h" #include "enc-util.h" +#include "cursor.h" #include #include @@ -518,6 +519,39 @@ static void on_encoder_push_done(struct encoder* encoder, struct rcbuf* payload) process_fb_update_requests(client); } +static void send_cursor_update(struct nvnc_client* client) +{ + struct nvnc* server = client->server; + + if (!server->cursor.buffer) { + // TODO: Empty buffer means that no cursor should be visible + return; + } + + struct vec payload; + vec_init(&payload, 4096); + + struct rfb_server_fb_update_msg head = { + .type = RFB_SERVER_TO_CLIENT_FRAMEBUFFER_UPDATE, + .n_rects = htons(1), + }; + + vec_append(&payload, &head, sizeof(head)); + + int rc = cursor_encode(&payload, &client->pixfmt, server->cursor.buffer, + server->cursor.x_hotspot, server->cursor.y_hotspot); + if (rc < 0) { + log_error("Failed to send cursor to client\n"); + vec_destroy(&payload); + return; + } + + client->cursor_seq = server->cursor_seq; + + stream_send(client->net_stream, rcbuf_new(payload.data, payload.len), + NULL, NULL); +} + static void process_fb_update_requests(struct nvnc_client* client) { struct nvnc* server = client->server; @@ -528,9 +562,6 @@ static void process_fb_update_requests(struct nvnc_client* client) if (client->net_stream->state == STREAM_STATE_CLOSED) return; - if (!pixman_region_not_empty(&client->damage)) - return; - if (client->is_updating || client->n_pending_requests == 0) return; @@ -559,6 +590,17 @@ static void process_fb_update_requests(struct nvnc_client* client) return; } + if (server->cursor_seq != client->cursor_seq + && client_has_encoding(client, RFB_ENCODING_CURSOR)) { + send_cursor_update(client); + + if (--client->n_pending_requests <= 0) + return; + } + + if (!pixman_region_not_empty(&client->damage)) + return; + DTRACE_PROBE1(neatvnc, update_fb_start, client); enum rfb_encodings encoding = choose_frame_encoding(client, fb); @@ -1533,3 +1575,36 @@ cert_alloc_failure: #endif return -1; } + +EXPORT +void nvnc_set_cursor(struct nvnc* self, struct nvnc_fb* fb, uint16_t x_hotspot, + uint16_t y_hotspot, struct pixman_region16* damage) +{ + if (self->cursor.buffer) { + nvnc_fb_release(self->cursor.buffer); + nvnc_fb_unref(self->cursor.buffer); + } + + self->cursor.buffer = fb; + if (!fb) { + self->cursor.x_hotspot = 0; + self->cursor.y_hotspot = 0; + return; + } + + nvnc_fb_ref(fb); + nvnc_fb_hold(fb); + + self->cursor.x_hotspot = x_hotspot; + self->cursor.y_hotspot = y_hotspot; + + if (!damage || pixman_region_not_empty(damage)) { + // TODO: Hash cursors to check if they actually changed + + self->cursor_seq++; + + struct nvnc_client* client; + LIST_FOREACH(client, &self->clients, link) + process_fb_update_requests(client); + } +}