diff --git a/include/data-control.h b/include/data-control.h new file mode 100644 index 0000000..9caf87e --- /dev/null +++ b/include/data-control.h @@ -0,0 +1,24 @@ +#include "wlr-data-control-unstable-v1.h" +#include "seat.h" + +typedef void (*vnc_write_clipboard_t)(char* text, size_t size); + +struct data_control { + struct wl_display* wl_display; + struct nvnc* server; + struct zwlr_data_control_manager_v1* manager; + struct zwlr_data_control_device_v1* device; + struct zwlr_data_control_source_v1* selection; + struct zwlr_data_control_source_v1* primary_selection; + struct zwlr_data_control_offer_v1* offer; + const char* mime_type; + char* cb_data; + size_t cb_len; + vnc_write_clipboard_t vnc_write_clipboard; +}; + +void data_control_init(struct data_control* self, struct seat* seat, struct zwlr_data_control_manager_v1 *manager); +void data_control_to_clipboard(struct data_control* self, const char* text, size_t len); +void data_control_destroy(struct data_control* self); + +#pragma once \ No newline at end of file diff --git a/include/vnc.h b/include/vnc.h index b6d0acf..300f16d 100644 --- a/include/vnc.h +++ b/include/vnc.h @@ -17,6 +17,7 @@ #pragma once #include "rfbclient.h" +#include "data-control.h" #include #include @@ -36,6 +37,7 @@ struct vnc_av_frame { struct vnc_client { rfbClient* client; + struct data_control* data_control; struct open_h264* open_h264; bool current_rect_is_av_frame; struct vnc_av_frame* av_frames[VNC_CLIENT_MAX_AV_FRAMES]; @@ -53,7 +55,7 @@ struct vnc_client { bool is_updating; }; -struct vnc_client* vnc_client_create(void); +struct vnc_client* vnc_client_create(struct data_control* data_control); void vnc_client_destroy(struct vnc_client* self); int vnc_client_connect(struct vnc_client* self, const char* address, int port); @@ -79,4 +81,5 @@ void vnc_client_set_compression_level(struct vnc_client* self, int value); void vnc_client_send_cut_text(struct vnc_client* self, const char* text, size_t len); void vnc_client_clear_av_frames(struct vnc_client* self); -rfbCredential* handle_vnc_authentication(struct _rfbClient *client, int credentialType); \ No newline at end of file +rfbCredential* handle_vnc_authentication(struct _rfbClient *client, int credentialType); +void cut_text (struct vnc_client* self, const char* text, size_t size); \ No newline at end of file diff --git a/meson.build b/meson.build index d4ae9c4..f8d7f28 100644 --- a/meson.build +++ b/meson.build @@ -70,6 +70,7 @@ sources = [ 'src/pointer.c', 'src/keyboard.c', 'src/vnc.c', + 'src/data-control.c', 'src/strlcpy.c', 'src/evdev-to-qnum.c', 'src/pixels.c', diff --git a/protocols/meson.build b/protocols/meson.build index 150ba64..8838d8a 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -16,6 +16,7 @@ wayland_scanner_client = generator( client_protocols = [ 'xdg-shell.xml', 'linux-dmabuf-unstable-v1.xml', + 'wlr-data-control-unstable-v1.xml' ] client_protos_src = [] diff --git a/protocols/wlr-data-control-unstable-v1.xml b/protocols/wlr-data-control-unstable-v1.xml new file mode 100644 index 0000000..75e8671 --- /dev/null +++ b/protocols/wlr-data-control-unstable-v1.xml @@ -0,0 +1,278 @@ + + + + Copyright © 2018 Simon Ser + Copyright © 2019 Ivan Molodetskikh + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + This protocol allows a privileged client to control data devices. In + particular, the client will be able to manage the current selection and take + the role of a clipboard manager. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + This interface is a manager that allows creating per-seat data device + controls. + + + + + Create a new data source. + + + + + + + Create a data device that can be used to manage a seat's selection. + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + + This interface allows a client to manage a seat's selection. + + When the seat is destroyed, this object becomes inert. + + + + + This request asks the compositor to set the selection to the data from + the source on behalf of the client. + + The given source may not be used in any further set_selection or + set_primary_selection requests. Attempting to use a previously used + source is a protocol error. + + To unset the selection, set the source to NULL. + + + + + + + Destroys the data device object. + + + + + + The data_offer event introduces a new wlr_data_control_offer object, + which will subsequently be used in either the + wlr_data_control_device.selection event (for the regular clipboard + selections) or the wlr_data_control_device.primary_selection event (for + the primary clipboard selections). Immediately following the + wlr_data_control_device.data_offer event, the new data_offer object + will send out wlr_data_control_offer.offer events to describe the MIME + types it offers. + + + + + + + The selection event is sent out to notify the client of a new + wlr_data_control_offer for the selection for this device. The + wlr_data_control_device.data_offer and the wlr_data_control_offer.offer + events are sent out immediately before this event to introduce the data + offer object. The selection event is sent to a client when a new + selection is set. The wlr_data_control_offer is valid until a new + wlr_data_control_offer or NULL is received. The client must destroy the + previous selection wlr_data_control_offer, if any, upon receiving this + event. + + The first selection event is sent upon binding the + wlr_data_control_device object. + + + + + + + This data control object is no longer valid and should be destroyed by + the client. + + + + + + + + The primary_selection event is sent out to notify the client of a new + wlr_data_control_offer for the primary selection for this device. The + wlr_data_control_device.data_offer and the wlr_data_control_offer.offer + events are sent out immediately before this event to introduce the data + offer object. The primary_selection event is sent to a client when a + new primary selection is set. The wlr_data_control_offer is valid until + a new wlr_data_control_offer or NULL is received. The client must + destroy the previous primary selection wlr_data_control_offer, if any, + upon receiving this event. + + If the compositor supports primary selection, the first + primary_selection event is sent upon binding the + wlr_data_control_device object. + + + + + + + This request asks the compositor to set the primary selection to the + data from the source on behalf of the client. + + The given source may not be used in any further set_selection or + set_primary_selection requests. Attempting to use a previously used + source is a protocol error. + + To unset the primary selection, set the source to NULL. + + The compositor will ignore this request if it does not support primary + selection. + + + + + + + + + + + + The wlr_data_control_source object is the source side of a + wlr_data_control_offer. It is created by the source client in a data + transfer and provides a way to describe the offered data and a way to + respond to requests to transfer the data. + + + + + + + + + This request adds a MIME type to the set of MIME types advertised to + targets. Can be called several times to offer multiple types. + + Calling this after wlr_data_control_device.set_selection is a protocol + error. + + + + + + + Destroys the data source object. + + + + + + Request for data from the client. Send the data as the specified MIME + type over the passed file descriptor, then close it. + + + + + + + + This data source is no longer valid. The data source has been replaced + by another data source. + + The client should clean up and destroy this data source. + + + + + + + A wlr_data_control_offer represents a piece of data offered for transfer + by another client (the source client). The offer describes the different + MIME types that the data can be converted to and provides the mechanism + for transferring the data directly from the source client. + + + + + To transfer the offered data, the client issues this request and + indicates the MIME type it wants to receive. The transfer happens + through the passed file descriptor (typically created with the pipe + system call). The source client writes the data in the MIME type + representation requested and then closes the file descriptor. + + The receiving client reads from the read end of the pipe until EOF and + then closes its end, at which point the transfer is complete. + + This request may happen multiple times for different MIME types. + + + + + + + + Destroys the data offer object. + + + + + + Sent immediately after creating the wlr_data_control_offer object. + One event per offered MIME type. + + + + + diff --git a/src/data-control.c b/src/data-control.c new file mode 100644 index 0000000..96bec2c --- /dev/null +++ b/src/data-control.c @@ -0,0 +1,308 @@ +#include "data-control.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct receive_context { + //struct data_control* data_control; + struct zwlr_data_control_offer_v1* offer; + int fd; + FILE* mem_fp; + size_t mem_size; + char* mem_data; + struct data_control* self; +}; + +static char* mime_type = "text/plain;charset=utf-8"; + +static bool isStringEqual(int lena, char* a, int lenb, char* b) { + if (lena != lenb) { + return false; + } + + // Compare every character + for (size_t i = 0; i < lena; ++i) { + if (a[i] != b[i]) { + return false; + } + } + + return true; +} + +static void on_receive(void* handler) +{ + struct receive_context* ctx = aml_get_userdata(handler); + int fd = aml_get_fd(handler); + assert(ctx->fd == fd); + + char buf[4096]; + + ssize_t ret = read(fd, &buf, sizeof(buf)); + if (ret > 0) { + fwrite(&buf, 1, ret, ctx->mem_fp); + return; + } + + fclose(ctx->mem_fp); + ctx->mem_fp = NULL; + + if (ctx->mem_size && ctx->self->vnc_write_clipboard) { + // If we receive clipboard data from the VNC server, we set the clipboard of the client. + // This "change" of clipboard data results into this "on_receive" event. That would leed + // to an endless loop... + // So we check if we send exactle that clipboard string to avoid an loop + if (!ctx->self->cb_data || !isStringEqual(ctx->self->cb_len, ctx->self->cb_data, strlen(ctx->mem_data), ctx->mem_data)) { + //printf("Received data FROM clipboard: %s\n", ctx->mem_data); + ctx->self->vnc_write_clipboard(ctx->mem_data, ctx->mem_size); + } + } + + aml_stop(aml_get_default(), handler); +} + +static void destroy_receive_context(void* raw_ctx) +{ + struct receive_context* ctx = raw_ctx; + int fd = ctx->fd; + + if (ctx->mem_fp) + fclose(ctx->mem_fp); + free(ctx->mem_data); + zwlr_data_control_offer_v1_destroy(ctx->offer); + close(fd); + free(ctx); +} + +static void receive_data(void* data, + struct zwlr_data_control_offer_v1* offer) +{ + struct data_control* self = data; + int pipe_fd[2]; + + if (pipe(pipe_fd) == -1) { + printf("pipe() failed"); + return; + } + + struct receive_context* ctx = calloc(1, sizeof(*ctx)); + if (!ctx) { + printf("OOM"); + close(pipe_fd[0]); + close(pipe_fd[1]); + return; + } + + zwlr_data_control_offer_v1_receive(self->offer, mime_type, pipe_fd[1]); + //wl_display_flush(self->wl_display); + close(pipe_fd[1]); + + ctx->self = self; + ctx->fd = pipe_fd[0]; + //ctx->data_control = self; + ctx->offer = self->offer; + ctx->mem_fp = open_memstream(&ctx->mem_data, &ctx->mem_size); + if (!ctx->mem_fp) { + close(ctx->fd); + free(ctx); + printf("open_memstream() failed"); + return; + } + + struct aml_handler* handler = aml_handler_new(ctx->fd, on_receive, + ctx, destroy_receive_context); + if (!handler) { + close(ctx->fd); + free(ctx); + return; + } + + aml_start(aml_get_default(), handler); + aml_unref(handler); +} + +static void data_control_offer(void* data, + struct zwlr_data_control_offer_v1* zwlr_data_control_offer_v1, + const char* mime_type) +{ + struct data_control* self = data; + + if (self->offer) + return; + if (strcmp(mime_type, mime_type) != 0) { + return; + } + + self->offer = zwlr_data_control_offer_v1; +} + +struct zwlr_data_control_offer_v1_listener data_control_offer_listener = { + data_control_offer +}; + +static void data_control_device_offer(void* data, + struct zwlr_data_control_device_v1* zwlr_data_control_device_v1, + struct zwlr_data_control_offer_v1* id) +{ + if (!id) + return; + + zwlr_data_control_offer_v1_add_listener(id, &data_control_offer_listener, data); +} + +static void data_control_device_selection(void* data, + struct zwlr_data_control_device_v1* zwlr_data_control_device_v1, + struct zwlr_data_control_offer_v1* id) +{ + struct data_control* self = data; + if (id && self->offer == id) { + receive_data(data, id); + self->offer = NULL; + } +} + +static void data_control_device_primary_selection(void* data, + struct zwlr_data_control_device_v1* zwlr_data_control_device_v1, + struct zwlr_data_control_offer_v1* id) +{ + struct data_control* self = data; + if (id && self->offer == id) { + receive_data(data, id); + self->offer = NULL; + return; + } +} + +static void data_control_device_finished(void* data, + struct zwlr_data_control_device_v1* zwlr_data_control_device_v1) +{ + zwlr_data_control_device_v1_destroy(zwlr_data_control_device_v1); +} + +static struct zwlr_data_control_device_v1_listener data_control_device_listener = { + .data_offer = data_control_device_offer, + .selection = data_control_device_selection, + .finished = data_control_device_finished, + .primary_selection = data_control_device_primary_selection +}; + + +static void +data_control_source_send(void* data, + struct zwlr_data_control_source_v1* zwlr_data_control_source_v1, + const char* mime_type, + int32_t fd) +{ + struct data_control* self = data; + char* d = self->cb_data; + size_t len = self->cb_len; + int ret; + + assert(d); + + ret = write(fd, d, len); + + if (ret < (int)len) + printf("write from clipboard incomplete"); + + close(fd); +} + +static void data_control_source_cancelled(void* data, + struct zwlr_data_control_source_v1* zwlr_data_control_source_v1) +{ + struct data_control* self = data; + + if (self->selection == zwlr_data_control_source_v1) { + self->selection = NULL; + } + if (self->primary_selection == zwlr_data_control_source_v1) { + self->primary_selection = NULL; + } + zwlr_data_control_source_v1_destroy(zwlr_data_control_source_v1); +} + +struct zwlr_data_control_source_v1_listener data_control_source_listener = { + .send = data_control_source_send, + .cancelled = data_control_source_cancelled +}; + +static struct zwlr_data_control_source_v1* set_selection(struct data_control* self, bool primary) { + struct zwlr_data_control_source_v1* selection; + selection = zwlr_data_control_manager_v1_create_data_source(self->manager); + if (selection == NULL) { + printf("zwlr_data_control_manager_v1_create_data_source() failed"); + free(self->cb_data); + self->cb_data = NULL; + return NULL; + } + + zwlr_data_control_source_v1_add_listener(selection, &data_control_source_listener, self); + zwlr_data_control_source_v1_offer(selection, mime_type); + + if (primary) + zwlr_data_control_device_v1_set_primary_selection(self->device, selection); + else + zwlr_data_control_device_v1_set_selection(self->device, selection); + + return selection; +} + +void data_control_init(struct data_control* self, struct seat* seat, struct zwlr_data_control_manager_v1 *manager) { + self->manager = manager; + self->device = zwlr_data_control_manager_v1_get_data_device(self->manager, seat->wl_seat); + self->cb_data = NULL; + self->cb_len = 0; + + zwlr_data_control_device_v1_add_listener(self->device, &data_control_device_listener, self); +} + +void data_control_to_clipboard(struct data_control* self, const char* text, size_t len) +{ + //printf("Writing text TO CLIPBOARD: %s\n", text); + if (!len) { + printf("%s called with 0 length", __func__); + return; + } + if (self->cb_data) { + free(self->cb_data); + } + + + self->cb_data = malloc(len); + if (!self->cb_data) { + printf("OOM"); + return; + } + + memcpy(self->cb_data, text, len); + self->cb_len = len; + // Set copy/paste buffer + self->selection = set_selection(self, false); + // Set highlight/middle_click buffer + self->primary_selection = set_selection(self, true); +} + +void data_control_destroy(struct data_control* self) +{ + if (self->selection) { + zwlr_data_control_source_v1_destroy(self->selection); + self->selection = NULL; + } + if (self->primary_selection) { + zwlr_data_control_source_v1_destroy(self->primary_selection); + self->primary_selection = NULL; + } + zwlr_data_control_device_v1_destroy(self->device); + free(self->cb_data); +} \ No newline at end of file diff --git a/src/main.c b/src/main.c index 82b0688..b1780e9 100644 --- a/src/main.c +++ b/src/main.c @@ -47,6 +47,7 @@ #include "linux-dmabuf-unstable-v1.h" #include "time-util.h" #include "output.h" +#include "data-control.h" #define CANARY_TICK_PERIOD INT64_C(100000) // us #define CANARY_LETHALITY_LEVEL INT64_C(8000) // us @@ -87,6 +88,9 @@ struct pointer_collection* pointers; struct keyboard_collection* keyboards; static int drm_fd = -1; static uint64_t last_canary_tick; +static struct data_control* data_control; +static struct zwlr_data_control_manager_v1 *manager; +static struct vnc_client* vnc; static bool have_egl = false; @@ -114,6 +118,9 @@ static void on_seat_capability_change(struct seat* seat) struct wl_keyboard* wl_keyboard = wl_seat_get_keyboard(seat->wl_seat); keyboard_collection_add_wl_keyboard(keyboards, wl_keyboard); + + data_control = malloc(sizeof(data_control)); + data_control_init(data_control, seat, manager); } else { // TODO Remove } @@ -155,6 +162,8 @@ static void registry_add(void* data, struct wl_registry* registry, uint32_t id, } wl_list_insert(&outputs, &output->link); + } else if (strcmp(interface, zwlr_data_control_manager_v1_interface.name) == 0) { + manager = wl_registry_bind(registry, id, &zwlr_data_control_manager_v1_interface, 2); } } @@ -815,6 +824,10 @@ static void create_canary_ticker(void) aml_unref(ticker); } +static void vnc_send_clipboard(char* text, size_t size) { + vnc_client_send_cut_text(vnc, text, size); +} + void run_main_loop_once(void) { struct aml* aml = aml_get_default(); @@ -964,12 +977,13 @@ int main(int argc, char* argv[]) wl_display_roundtrip(wl_display); wl_display_roundtrip(wl_display); - struct vnc_client* vnc = vnc_client_create(); + vnc = vnc_client_create(data_control); if (!vnc) goto vnc_failure; vnc->alloc_fb = on_vnc_client_alloc_fb; vnc->update_fb = on_vnc_client_update_fb; + data_control->vnc_write_clipboard = vnc_send_clipboard; if (vnc_client_set_pixel_format(vnc, shm_format) < 0) { fprintf(stderr, "Unsupported pixel format\n"); @@ -1049,5 +1063,9 @@ display_failure: signal_handler_failure: aml_unref(aml); printf("Exiting...\n"); + + // @TODO this will throw an segfault (can't determine proxy version, but why?) + if (data_control) + data_control_destroy(data_control); return rc; } diff --git a/src/tls_gnutls.c b/src/tls_gnutls.c index b9eb2d9..5d8576b 100644 --- a/src/tls_gnutls.c +++ b/src/tls_gnutls.c @@ -112,7 +112,7 @@ verify_certificate_callback (gnutls_session_t session) return GNUTLS_E_CERTIFICATE_ERROR; } - // Certificate doesn't have a hostname + // Hostname verification does NOT work //if (!gnutls_x509_crt_check_hostname (cert, hostname)) // { // rfbClientLog("The certificate's owner does not match hostname '%s'\n", diff --git a/src/vnc.c b/src/vnc.c index 788c450..0feb542 100644 --- a/src/vnc.c +++ b/src/vnc.c @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include "rfbclient.h" #include "vnc.h" @@ -134,6 +136,9 @@ static void vnc_client_got_cut_text(rfbClient* client, const char* text, if (self->cut_text) self->cut_text(self, text, len); + else { + printf("Cut text is not defined!\n"); + } } static rfbBool vnc_client_handle_open_h264_rect(rfbClient* client, @@ -216,7 +221,7 @@ static void vnc_client_init_pts_ext(void) rfbClientRegisterExtension(&ext); } -struct vnc_client* vnc_client_create(void) +struct vnc_client* vnc_client_create(struct data_control* data_control) { vnc_client_init_open_h264(); vnc_client_init_pts_ext(); @@ -238,6 +243,7 @@ struct vnc_client* vnc_client_create(void) goto failure; self->client = client; + self->data_control = data_control; rfbClientSetClientData(client, NULL, self); client->MallocFrameBuffer = vnc_client_alloc_fb; @@ -246,6 +252,7 @@ struct vnc_client* vnc_client_create(void) client->StartingFrameBufferUpdate = vnc_client_start_update; client->CancelledFrameBufferUpdate = vnc_client_cancel_update; client->GotXCutText = vnc_client_got_cut_text; + self->cut_text = cut_text; self->pts = NO_PTS; @@ -264,7 +271,7 @@ rfbCredential* handle_vnc_authentication(struct _rfbClient *client, int credenti if (client->authScheme == rfbVeNCrypt && credentialType == rfbCredentialTypeX509) { char* path = getenv("TLS_CA"); - rfbClientLog("Using TLS CA certificate from env 'TLS_CA': %s", path); + rfbClientLog("Using TLS CA certificate from env 'TLS_CA': %s\n", path); creds->x509Credential.x509CACertFile = malloc(strlen(path) + 1); strcpy(creds->x509Credential.x509CACertFile, path); @@ -272,7 +279,7 @@ rfbCredential* handle_vnc_authentication(struct _rfbClient *client, int credenti } else if (client->authScheme == rfbVeNCrypt && credentialType == rfbCredentialTypeUser) { const* username = getenv("VNC_USERNAME"); const* password = getenv("VNC_PASSWORD"); - rfbClientLog("Using username and password for VNC authentication 'VNC_USERNAME', 'VNC_PASSWORD'"); + rfbClientLog("Using username and password for VNC authentication 'VNC_USERNAME', 'VNC_PASSWORD'\n"); creds->userCredential.password = malloc(strlen(password) + 1); creds->userCredential.username = malloc(strlen(username) + 1); @@ -284,6 +291,11 @@ rfbCredential* handle_vnc_authentication(struct _rfbClient *client, int credenti return creds; } +void cut_text (struct vnc_client* self, const char* text, size_t size) { + data_control_to_clipboard(self->data_control, text, size); + //printf("Received string FROM vnc_server: %s\n", text); +} + void vnc_client_destroy(struct vnc_client* self) { vnc_client_clear_av_frames(self);