From 0a68dbb720d5fd4eb873ee4b0ba251df1b121057 Mon Sep 17 00:00:00 2001 From: Andri Yngvason Date: Sat, 11 Jul 2020 17:54:35 +0000 Subject: [PATCH] Create a nicer API around libvncclient This hides some implementation details of libvncclient from the rest of the system and reduces coupling. --- README.md | 1 + include/vnc.h | 37 ++++++++++ meson.build | 3 + src/main.c | 165 +++++++++++++++++++---------------------- src/vnc.c | 200 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 315 insertions(+), 91 deletions(-) create mode 100644 include/vnc.h create mode 100644 src/vnc.c diff --git a/README.md b/README.md index 17dba27..019b69d 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Expect bugs and missing features. * libvncclient * libwayland * libxkbcommon + * pixman ## Build Dependencies * GCC/clang diff --git a/include/vnc.h b/include/vnc.h new file mode 100644 index 0000000..cdc9cef --- /dev/null +++ b/include/vnc.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include +#include + +struct vnc_client { + rfbClient* client; + + int (*alloc_fb)(struct vnc_client*); + void (*update_fb)(struct vnc_client*); + + void* userdata; + struct pixman_region16 damage; +}; + +struct vnc_client* vnc_client_create(void); +void vnc_client_destroy(struct vnc_client* self); + +int vnc_client_connect(struct vnc_client* self, const char* address, int port); + +int vnc_client_set_pixel_format(struct vnc_client* self, + enum wl_shm_format format); + +int vnc_client_get_fd(const struct vnc_client* self); +int vnc_client_get_width(const struct vnc_client* self); +int vnc_client_get_height(const struct vnc_client* self); +int vnc_client_get_stride(const struct vnc_client* self); +void* vnc_client_get_fb(const struct vnc_client* self); +void vnc_client_set_fb(struct vnc_client* self, void* fb); +const char* vnc_client_get_desktop_name(const struct vnc_client* self); +int vnc_client_process(struct vnc_client* self); +void vnc_client_send_pointer_event(struct vnc_client* self, int x, int y, + uint32_t button_mask); +void vnc_client_send_keyboard_event(struct vnc_client* self, uint32_t symbol, + bool is_pressed); diff --git a/meson.build b/meson.build index e8d24b7..b8b96ac 100644 --- a/meson.build +++ b/meson.build @@ -28,6 +28,7 @@ libm = cc.find_library('m', required: false) librt = cc.find_library('rt', required: false) xkbcommon = dependency('xkbcommon') +pixman = dependency('pixman-1') wayland_client = dependency('wayland-client') libvncclient = dependency('libvncclient') @@ -48,6 +49,7 @@ sources = [ 'src/seat.c', 'src/pointer.c', 'src/keyboard.c', + 'src/vnc.c', 'src/strlcpy.c', ] @@ -55,6 +57,7 @@ dependencies = [ libm, librt, xkbcommon, + pixman, aml, wayland_client, libvncclient, diff --git a/src/main.c b/src/main.c index 2648fa7..bbc22e3 100644 --- a/src/main.c +++ b/src/main.c @@ -26,14 +26,15 @@ #include #include #include -#include #include +#include "pixman.h" #include "xdg-shell.h" #include "shm.h" #include "seat.h" #include "pointer.h" #include "keyboard.h" +#include "vnc.h" struct buffer { int width, height, stride; @@ -49,7 +50,6 @@ struct window { struct xdg_toplevel* xdg_toplevel; struct buffer* buffer; - bool is_attached; }; static struct wl_display* wl_display; @@ -357,18 +357,18 @@ static void window_destroy(struct window* w) void on_pointer_event(struct pointer_collection* collection, struct pointer* pointer) { - rfbClient* client = collection->userdata; + struct vnc_client* client = collection->userdata; int x = wl_fixed_to_int(pointer->x); int y = wl_fixed_to_int(pointer->y); - SendPointerEvent(client, x, y, pointer->pressed); + vnc_client_send_pointer_event(client, x, y, pointer->pressed); } void on_keyboard_event(struct keyboard_collection* collection, struct keyboard* keyboard, uint32_t key, bool is_pressed) { - rfbClient* client = collection->userdata; + struct vnc_client* client = collection->userdata; // TODO handle multiple symbols xkb_keysym_t symbol = xkb_state_key_get_one_sym(keyboard->state, key); @@ -376,109 +376,81 @@ void on_keyboard_event(struct keyboard_collection* collection, char name[256]; xkb_keysym_get_name(symbol, name, sizeof(name)); - SendKeyEvent(client, symbol, is_pressed); + vnc_client_send_keyboard_event(client, symbol, is_pressed); } -rfbBool rfb_client_alloc_fb(rfbClient* cl) +int on_vnc_client_alloc_fb(struct vnc_client* client) { - int stride = cl->width * 4; // TODO? - assert(!window); // TODO: Support resizing - window = window_create(cl->desktopName); + int width = vnc_client_get_width(client); + int height = vnc_client_get_height(client); + int stride = vnc_client_get_stride(client); + + window = window_create(vnc_client_get_desktop_name(client)); if (!window) - return FALSE; - - window->buffer = buffer_create(cl->width, cl->height, stride, - wl_shm_format); - - cl->frameBuffer = window->buffer->pixels; - - return TRUE; -} - -void rfb_client_update_box(rfbClient* cl, int x, int y, int width, int height) -{ - // TODO: Make sure that the buffer is released at this point, or make - // this a side-buffer and copy damaged regions into double buffers. - - if (!window->is_attached) - window_attach(window, 0, 0); - - window_damage(window, x, y, width, height); -} - -void rfb_client_finish_update(rfbClient* cl) -{ - window_commit(window); -} - -void on_rfb_client_server_event(void* obj) -{ - rfbClient* cl = aml_get_userdata(obj); - if (!HandleRFBServerMessage(cl)) - do_run = false; -} - -static int rfb_format_from_wl_shm_format(rfbPixelFormat* dst, - enum wl_shm_format src) -{ - int bpp = -1; - - switch (src) { - case WL_SHM_FORMAT_ARGB8888: - case WL_SHM_FORMAT_XRGB8888: - dst->redShift = 16; - dst->greenShift = 8; - dst->blueShift = 0; - bpp = 32; - break; - default: return -1; - } - - switch (bpp) { - case 32: - dst->bitsPerPixel = 32; - dst->depth = 24; - dst->redMax = 0xff; - dst->greenMax = 0xff; - dst->blueMax = 0xff; - break; - default: - abort(); - } + window->buffer = buffer_create(width, height, stride, wl_shm_format); + vnc_client_set_fb(client, window->buffer->pixels); return 0; } -static rfbClient* rfb_client_create(int* argc, char* argv[]) +void on_vnc_client_update_fb(struct vnc_client* client) { - int bits_per_sample = 8; - int samples_per_pixel = 3; - int bytes_per_pixel = 4; + if (!pixman_region_not_empty(&client->damage)) + return; - rfbClient* cl = rfbGetClient(bits_per_sample, samples_per_pixel, - bytes_per_pixel); - if (!cl) + // TODO: Make sure that the buffer is released at this point, or make + // this a side-buffer and copy damaged regions into double buffers. + window_attach(window, 0, 0); + + int n_rects = 0; + struct pixman_box16* box = pixman_region_rectangles(&client->damage, + &n_rects); + + for (int i = 0; i < n_rects; ++i) { + int x = box[i].x1; + int y = box[i].y1; + int width = box[i].x2 - x; + int height = box[i].y2 - y; + + window_damage(window, x, y, width, height); + } + + window_commit(window); +} + +void on_vnc_client_event(void* obj) +{ + struct vnc_client* client = aml_get_userdata(obj); + if (vnc_client_process(client) < 0) + do_run = false; +} + +static struct vnc_client* connect_to_server(const char* address, int port) +{ + struct vnc_client* client = vnc_client_create(); + if (!client) return NULL; - cl->MallocFrameBuffer = rfb_client_alloc_fb; - cl->GotFrameBufferUpdate = rfb_client_update_box; - cl->FinishedFrameBufferUpdate = rfb_client_finish_update; + client->alloc_fb = on_vnc_client_alloc_fb; + client->update_fb = on_vnc_client_update_fb; - if (rfb_format_from_wl_shm_format(&cl->format, wl_shm_format) < 0) { + if (vnc_client_set_pixel_format(client, wl_shm_format) < 0) { fprintf(stderr, "Unsupported pixel format\n"); return NULL; } - if (!rfbInitClient(cl, argc, argv)) - return NULL; + if (vnc_client_connect(client, address, port) < 0) { + fprintf(stderr, "Failed to connect to server\n"); + goto failure; + } - int fd = cl->sock; + int fd = vnc_client_get_fd(client); struct aml_handler* handler; - handler = aml_handler_new(fd, on_rfb_client_server_event, cl, NULL); + handler = aml_handler_new(fd, on_vnc_client_event, client, NULL); if (!handler) goto failure; @@ -488,22 +460,33 @@ static rfbClient* rfb_client_create(int* argc, char* argv[]) if (rc < 0) goto failure; - return cl; + return client; failure: - rfbClientCleanup(cl); + vnc_client_destroy(client); return NULL; } -static void rfb_client_destroy(rfbClient* cl) +static int usage(int r) { - rfbClientCleanup(cl); + fprintf(r ? stderr : stdout, "\ +Usage: wlvncc
[port]\n\ +"); + return r; } int main(int argc, char* argv[]) { int rc = -1; + if (argc < 2) + return usage(1); + + const char* address = argv[1]; + int port = 5900; + if (argc > 2) + port = atoi(argv[2]); + struct aml* aml = aml_new(); if (!aml) return 1; @@ -550,7 +533,7 @@ int main(int argc, char* argv[]) wl_display_roundtrip(wl_display); wl_display_roundtrip(wl_display); - rfbClient* vnc = rfb_client_create(&argc, argv); + struct vnc_client* vnc = connect_to_server(address, port); if (!vnc) goto vnc_failure; @@ -568,7 +551,7 @@ int main(int argc, char* argv[]) rc = 0; if (window) window_destroy(window); - rfb_client_destroy(vnc); + vnc_client_destroy(vnc); vnc_failure: seat_list_destroy(&seats); wl_compositor_destroy(wl_compositor); diff --git a/src/vnc.c b/src/vnc.c new file mode 100644 index 0000000..e898b3e --- /dev/null +++ b/src/vnc.c @@ -0,0 +1,200 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vnc.h" + +static rfbBool vnc_client_alloc_fb(rfbClient* client) +{ + struct vnc_client* self = rfbClientGetClientData(client, NULL); + assert(self); + + return self->alloc_fb(self) < 0 ? FALSE : TRUE; +} + +static void vnc_client_update_box(rfbClient* client, int x, int y, int width, + int height) +{ + struct vnc_client* self = rfbClientGetClientData(client, NULL); + assert(self); + + pixman_region_union_rect(&self->damage, &self->damage, x, y, width, + height); +} + +static void vnc_client_finish_update(rfbClient* client) +{ + struct vnc_client* self = rfbClientGetClientData(client, NULL); + assert(self); + + self->update_fb(self); + + pixman_region_clear(&self->damage); +} + +struct vnc_client* vnc_client_create(void) +{ + struct vnc_client* self = calloc(1, sizeof(*self)); + if (!self) + return NULL; + + /* These are defaults that can be changed with + * vnc_client_set_pixel_format(). + */ + int bits_per_sample = 8; + int samples_per_pixel = 3; + int bytes_per_pixel = 4; + + rfbClient* client = rfbGetClient(bits_per_sample, samples_per_pixel, + bytes_per_pixel); + if (!client) + goto failure; + + self->client = client; + rfbClientSetClientData(client, NULL, self); + + client->MallocFrameBuffer = vnc_client_alloc_fb; + client->GotFrameBufferUpdate = vnc_client_update_box; + client->FinishedFrameBufferUpdate = vnc_client_finish_update; + + return self; + +failure: + free(self); + return NULL; +} + +void vnc_client_destroy(struct vnc_client* self) +{ + rfbClientCleanup(self->client); + free(self); +} + +int vnc_client_connect(struct vnc_client* self, const char* address, int port) +{ + rfbClient* client = self->client; + + if (!ConnectToRFBServer(client, address, port)) + return -1; + + if (!InitialiseRFBConnection(client)) + return -1; + + client->width = client->si.framebufferWidth; + client->height = client->si.framebufferHeight; + + if (!client->MallocFrameBuffer(client)) + return -1; + + if (!SetFormatAndEncodings(client)) + return -1; + + if (client->updateRect.x < 0) { + client->updateRect.x = client->updateRect.y = 0; + client->updateRect.w = client->width; + client->updateRect.h = client->height; + } + + if (!SendFramebufferUpdateRequest(client, + client->updateRect.x, client->updateRect.y, + client->updateRect.w, client->updateRect.h, + FALSE)) + return -1; + + return 0; +} + +int vnc_client_set_pixel_format(struct vnc_client* self, + enum wl_shm_format format) +{ + rfbPixelFormat* dst = &self->client->format; + int bpp = -1; + + switch (format) { + case WL_SHM_FORMAT_ARGB8888: + case WL_SHM_FORMAT_XRGB8888: + dst->redShift = 16; + dst->greenShift = 8; + dst->blueShift = 0; + bpp = 32; + break; + default: + return -1; + } + + switch (bpp) { + case 32: + dst->bitsPerPixel = 32; + dst->depth = 24; + dst->redMax = 0xff; + dst->greenMax = 0xff; + dst->blueMax = 0xff; + break; + default: + abort(); + } + + dst->trueColour = 1; + dst->bigEndian = FALSE; + self->client->appData.requestedDepth = dst->depth; + + return 0; +} + +int vnc_client_get_width(const struct vnc_client* self) +{ + return self->client->width; +} + +int vnc_client_get_height(const struct vnc_client* self) +{ + return self->client->height; +} + +int vnc_client_get_stride(const struct vnc_client* self) +{ + // TODO: What happens if bitsPerPixel == 24? + return self->client->width * self->client->format.bitsPerPixel / 8; +} + +void* vnc_client_get_fb(const struct vnc_client* self) +{ + return self->client->frameBuffer; +} + +void vnc_client_set_fb(struct vnc_client* self, void* fb) +{ + self->client->frameBuffer = fb; +} + +int vnc_client_get_fd(const struct vnc_client* self) +{ + return self->client->sock; +} + +const char* vnc_client_get_desktop_name(const struct vnc_client* self) +{ + return self->client->desktopName; +} + +int vnc_client_process(struct vnc_client* self) +{ + return HandleRFBServerMessage(self->client) ? 0 : -1; +} + +void vnc_client_send_pointer_event(struct vnc_client* self, int x, int y, + uint32_t button_mask) +{ + SendPointerEvent(self->client, x, y, button_mask); +} + +void vnc_client_send_keyboard_event(struct vnc_client* self, uint32_t symbol, + bool is_pressed) +{ + SendKeyEvent(self->client, symbol, is_pressed); +}