From b37e73488f460ec20f147a2584a50b2f07e74729 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 27 Nov 2017 09:38:20 -0800 Subject: [PATCH 1/8] GUACAMOLE-313: Provide reference to in-progress screen recording. --- src/common/common/recording.h | 35 +++++++++++++++++++++++++++++++---- src/common/recording.c | 22 +++++++++++++++------- src/protocols/rdp/client.c | 5 +++++ src/protocols/rdp/rdp.c | 2 +- src/protocols/rdp/rdp.h | 7 +++++++ src/protocols/ssh/client.c | 5 +++++ src/protocols/ssh/ssh.c | 2 +- src/protocols/ssh/ssh.h | 7 +++++++ src/protocols/telnet/client.c | 5 +++++ src/protocols/telnet/telnet.c | 2 +- src/protocols/telnet/telnet.h | 9 ++++++++- src/protocols/vnc/client.c | 5 +++++ src/protocols/vnc/vnc.c | 2 +- src/protocols/vnc/vnc.h | 7 +++++++ 14 files changed, 99 insertions(+), 16 deletions(-) diff --git a/src/common/common/recording.h b/src/common/common/recording.h index 71d8fbc7..0305a83c 100644 --- a/src/common/common/recording.h +++ b/src/common/common/recording.h @@ -42,6 +42,21 @@ */ #define GUAC_COMMON_RECORDING_MAX_NAME_LENGTH 2048 +/** + * An in-progress session recording, attached to a guac_client instance such + * that output Guacamole instructions may be dynamically intercepted and + * written to a file. + */ +typedef struct guac_common_recording { + + /** + * The guac_socket which writes directly to the recording file, rather than + * to any particular user. + */ + guac_socket* socket; + +} guac_common_recording; + /** * Replaces the socket of the given client such that all further Guacamole * protocol output will be copied into a file within the given path and having @@ -68,11 +83,23 @@ * exist. * * @return - * Zero if the recording file has been successfully created and a recording - * will be written, non-zero otherwise. + * A new guac_common_recording structure representing the in-progress + * recording if the recording file has been successfully created and a + * recording will be written, NULL otherwise. */ -int guac_common_recording_create(guac_client* client, const char* path, - const char* name, int create_path); +guac_common_recording* guac_common_recording_create(guac_client* client, + const char* path, const char* name, int create_path); + +/** + * Frees the resources associated with the given in-progress recording. Note + * that, due to the manner that recordings are attached to the guac_client, the + * underlying guac_socket is not freed. The guac_socket will be automatically + * freed when the guac_client is freed. + * + * @param recording + * The guac_common_recording to free. + */ +void guac_common_recording_free(guac_common_recording* recording); #endif diff --git a/src/common/recording.c b/src/common/recording.c index 6b1dcccc..274ac6de 100644 --- a/src/common/recording.c +++ b/src/common/recording.c @@ -133,8 +133,8 @@ static int guac_common_recording_open(const char* path, } -int guac_common_recording_create(guac_client* client, const char* path, - const char* name, int create_path) { +guac_common_recording* guac_common_recording_create(guac_client* client, + const char* path, const char* name, int create_path) { char filename[GUAC_COMMON_RECORDING_MAX_NAME_LENGTH]; @@ -146,7 +146,7 @@ int guac_common_recording_create(guac_client* client, const char* path, #endif guac_client_log(client, GUAC_LOG_ERROR, "Creation of recording failed: %s", strerror(errno)); - return 1; + return NULL; } /* Attempt to open recording file */ @@ -154,18 +154,26 @@ int guac_common_recording_create(guac_client* client, const char* path, if (fd == -1) { guac_client_log(client, GUAC_LOG_ERROR, "Creation of recording failed: %s", strerror(errno)); - return 1; + return NULL; } - /* Replace client socket with wrapped socket */ - client->socket = guac_socket_tee(client->socket, guac_socket_open(fd)); + /* Create recording structure with reference to underlying socket */ + guac_common_recording* recording = malloc(sizeof(guac_common_recording)); + recording->socket = guac_socket_open(fd); + + /* Replace client socket with wrapped recording socket */ + client->socket = guac_socket_tee(client->socket, recording->socket); /* Recording creation succeeded */ guac_client_log(client, GUAC_LOG_INFO, "Recording of session will be saved to \"%s\".", filename); - return 0; + return recording; } +void guac_common_recording_free(guac_common_recording* recording) { + free(recording); +} + diff --git a/src/protocols/rdp/client.c b/src/protocols/rdp/client.c index d12efbaa..6b9a0a6b 100644 --- a/src/protocols/rdp/client.c +++ b/src/protocols/rdp/client.c @@ -20,6 +20,7 @@ #include "config.h" #include "audio_input.h" +#include "common/recording.h" #include "client.h" #include "rdp.h" #include "rdp_disp.h" @@ -122,6 +123,10 @@ int guac_rdp_client_free_handler(guac_client* client) { guac_common_ssh_uninit(); #endif + /* Clean up recording, if in progress */ + if (rdp_client->recording != NULL) + guac_common_recording_free(rdp_client->recording); + /* Clean up audio stream, if allocated */ if (rdp_client->audio != NULL) guac_audio_stream_free(rdp_client->audio); diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index f21e30c5..a0de94da 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -668,7 +668,7 @@ static int guac_rdp_handle_connection(guac_client* client) { /* Set up screen recording, if requested */ if (settings->recording_path != NULL) { - guac_common_recording_create(client, + rdp_client->recording = guac_common_recording_create(client, settings->recording_path, settings->recording_name, settings->create_recording_path); diff --git a/src/protocols/rdp/rdp.h b/src/protocols/rdp/rdp.h index 943155dd..1d2eb110 100644 --- a/src/protocols/rdp/rdp.h +++ b/src/protocols/rdp/rdp.h @@ -26,6 +26,7 @@ #include "common/clipboard.h" #include "common/display.h" #include "common/list.h" +#include "common/recording.h" #include "common/surface.h" #include "keyboard.h" #include "rdp_disp.h" @@ -143,6 +144,12 @@ typedef struct guac_rdp_client { guac_common_ssh_sftp_filesystem* sftp_filesystem; #endif + /** + * The in-progress session recording, or NULL if no recording is in + * progress. + */ + guac_common_recording* recording; + /** * Display size update module. */ diff --git a/src/protocols/ssh/client.c b/src/protocols/ssh/client.c index 12eb1b71..47b5978f 100644 --- a/src/protocols/ssh/client.c +++ b/src/protocols/ssh/client.c @@ -20,6 +20,7 @@ #include "config.h" #include "client.h" +#include "common/recording.h" #include "common-ssh/sftp.h" #include "ssh.h" #include "terminal/terminal.h" @@ -88,6 +89,10 @@ int guac_ssh_client_free_handler(guac_client* client) { guac_common_ssh_destroy_session(ssh_client->sftp_session); } + /* Clean up recording, if in progress */ + if (ssh_client->recording != NULL) + guac_common_recording_free(ssh_client->recording); + /* Free interactive SSH session */ if (ssh_client->session != NULL) guac_common_ssh_destroy_session(ssh_client->session); diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c index 6575d9f3..d1b90413 100644 --- a/src/protocols/ssh/ssh.c +++ b/src/protocols/ssh/ssh.c @@ -193,7 +193,7 @@ void* ssh_client_thread(void* data) { /* Set up screen recording, if requested */ if (settings->recording_path != NULL) { - guac_common_recording_create(client, + ssh_client->recording = guac_common_recording_create(client, settings->recording_path, settings->recording_name, settings->create_recording_path); diff --git a/src/protocols/ssh/ssh.h b/src/protocols/ssh/ssh.h index 032e9209..0a89a2d1 100644 --- a/src/protocols/ssh/ssh.h +++ b/src/protocols/ssh/ssh.h @@ -22,6 +22,7 @@ #include "config.h" +#include "common/recording.h" #include "common-ssh/sftp.h" #include "common-ssh/ssh.h" #include "common-ssh/user.h" @@ -93,6 +94,12 @@ typedef struct guac_ssh_client { */ guac_terminal* term; + /** + * The in-progress session recording, or NULL if no recording is in + * progress. + */ + guac_common_recording* recording; + } guac_ssh_client ; /** diff --git a/src/protocols/telnet/client.c b/src/protocols/telnet/client.c index 2a53d263..3f64f1c2 100644 --- a/src/protocols/telnet/client.c +++ b/src/protocols/telnet/client.c @@ -19,6 +19,7 @@ #include "config.h" #include "client.h" +#include "common/recording.h" #include "settings.h" #include "telnet.h" #include "terminal/terminal.h" @@ -71,6 +72,10 @@ int guac_telnet_client_free_handler(guac_client* client) { if (telnet_client->socket_fd != -1) close(telnet_client->socket_fd); + /* Clean up recording, if in progress */ + if (telnet_client->recording != NULL) + guac_common_recording_free(telnet_client->recording); + /* Kill terminal */ guac_terminal_free(telnet_client->term); diff --git a/src/protocols/telnet/telnet.c b/src/protocols/telnet/telnet.c index 82f6dd4d..e135e3ed 100644 --- a/src/protocols/telnet/telnet.c +++ b/src/protocols/telnet/telnet.c @@ -467,7 +467,7 @@ void* guac_telnet_client_thread(void* data) { /* Set up screen recording, if requested */ if (settings->recording_path != NULL) { - guac_common_recording_create(client, + telnet_client->recording = guac_common_recording_create(client, settings->recording_path, settings->recording_name, settings->create_recording_path); diff --git a/src/protocols/telnet/telnet.h b/src/protocols/telnet/telnet.h index 8259507e..24ad4a8f 100644 --- a/src/protocols/telnet/telnet.h +++ b/src/protocols/telnet/telnet.h @@ -21,6 +21,7 @@ #define GUAC_TELNET_H #include "config.h" +#include "common/recording.h" #include "settings.h" #include "terminal/terminal.h" @@ -69,7 +70,13 @@ typedef struct guac_telnet_client { * The terminal which will render all output from the telnet client. */ guac_terminal* term; - + + /** + * The in-progress session recording, or NULL if no recording is in + * progress. + */ + guac_common_recording* recording; + } guac_telnet_client; /** diff --git a/src/protocols/vnc/client.c b/src/protocols/vnc/client.c index 00b90154..9cc85a18 100644 --- a/src/protocols/vnc/client.c +++ b/src/protocols/vnc/client.c @@ -19,6 +19,7 @@ #include "config.h" +#include "common/recording.h" #include "client.h" #include "user.h" #include "vnc.h" @@ -102,6 +103,10 @@ int guac_vnc_client_free_handler(guac_client* client) { guac_common_ssh_uninit(); #endif + /* Clean up recording, if in progress */ + if (vnc_client->recording != NULL) + guac_common_recording_free(vnc_client->recording); + /* Free clipboard */ if (vnc_client->clipboard != NULL) guac_common_clipboard_free(vnc_client->clipboard); diff --git a/src/protocols/vnc/vnc.c b/src/protocols/vnc/vnc.c index 38c7cd60..bc8b5e43 100644 --- a/src/protocols/vnc/vnc.c +++ b/src/protocols/vnc/vnc.c @@ -303,7 +303,7 @@ void* guac_vnc_client_thread(void* data) { /* Set up screen recording, if requested */ if (settings->recording_path != NULL) { - guac_common_recording_create(client, + vnc_client->recording = guac_common_recording_create(client, settings->recording_path, settings->recording_name, settings->create_recording_path); diff --git a/src/protocols/vnc/vnc.h b/src/protocols/vnc/vnc.h index 0edbcd47..ce2d20af 100644 --- a/src/protocols/vnc/vnc.h +++ b/src/protocols/vnc/vnc.h @@ -25,6 +25,7 @@ #include "common/clipboard.h" #include "common/display.h" #include "common/iconv.h" +#include "common/recording.h" #include "common/surface.h" #include "settings.h" @@ -110,6 +111,12 @@ typedef struct guac_vnc_client { guac_common_ssh_sftp_filesystem* sftp_filesystem; #endif + /** + * The in-progress session recording, or NULL if no recording is in + * progress. + */ + guac_common_recording* recording; + /** * Clipboard encoding-specific reader. */ From df770ae4ea09505d3eb72181b9dff9169926f0bb Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 27 Nov 2017 13:03:21 -0800 Subject: [PATCH 2/8] GUACAMOLE-313: Add support for including mouse location within session recordings. --- src/common/common/recording.h | 15 +++++++++++++++ src/common/recording.c | 9 +++++++++ src/protocols/rdp/input.c | 5 +++++ src/protocols/ssh/input.c | 5 +++++ src/protocols/telnet/input.c | 5 +++++ src/protocols/vnc/input.c | 5 +++++ 6 files changed, 44 insertions(+) diff --git a/src/common/common/recording.h b/src/common/common/recording.h index 0305a83c..e03e7881 100644 --- a/src/common/common/recording.h +++ b/src/common/common/recording.h @@ -101,5 +101,20 @@ guac_common_recording* guac_common_recording_create(guac_client* client, */ void guac_common_recording_free(guac_common_recording* recording); +/** + * Reports the current mouse position within the recording. + * + * @param recording + * The guac_common_recording associated with the mouse that has moved. + * + * @param x + * The new X coordinate of the mouse cursor, in pixels. + * + * @param y + * The new Y coordinate of the mouse cursor, in pixels. + */ +void guac_common_recording_report_mouse(guac_common_recording* recording, + int x, int y); + #endif diff --git a/src/common/recording.c b/src/common/recording.c index 274ac6de..4f334d5f 100644 --- a/src/common/recording.c +++ b/src/common/recording.c @@ -20,6 +20,7 @@ #include "common/recording.h" #include +#include #include #ifdef __MINGW32__ @@ -177,3 +178,11 @@ void guac_common_recording_free(guac_common_recording* recording) { free(recording); } +void guac_common_recording_report_mouse(guac_common_recording* recording, + int x, int y) { + + /* Report mouse location */ + guac_protocol_send_mouse(recording->socket, x, y); + +} + diff --git a/src/protocols/rdp/input.c b/src/protocols/rdp/input.c index 9e220496..7cb3da8f 100644 --- a/src/protocols/rdp/input.c +++ b/src/protocols/rdp/input.c @@ -20,6 +20,7 @@ #include "config.h" #include "client.h" +#include "common/recording.h" #include "input.h" #include "keyboard.h" #include "rdp.h" @@ -49,6 +50,10 @@ int guac_rdp_user_mouse_handler(guac_user* user, int x, int y, int mask) { /* Store current mouse location */ guac_common_cursor_move(rdp_client->display->cursor, user, x, y); + /* Report mouse position within recording */ + if (rdp_client->recording != NULL) + guac_common_recording_report_mouse(rdp_client->recording, x, y); + /* If button mask unchanged, just send move event */ if (mask == rdp_client->mouse_button_mask) rdp_inst->input->MouseEvent(rdp_inst->input, PTR_FLAGS_MOVE, x, y); diff --git a/src/protocols/ssh/input.c b/src/protocols/ssh/input.c index 95e01881..b4d0cf5c 100644 --- a/src/protocols/ssh/input.c +++ b/src/protocols/ssh/input.c @@ -21,6 +21,7 @@ #include "common/cursor.h" #include "common/display.h" +#include "common/recording.h" #include "ssh.h" #include "terminal/terminal.h" @@ -40,6 +41,10 @@ int guac_ssh_user_mouse_handler(guac_user* user, int x, int y, int mask) { if (term == NULL) return 0; + /* Report mouse position within recording */ + if (ssh_client->recording != NULL) + guac_common_recording_report_mouse(ssh_client->recording, x, y); + /* Send mouse event */ guac_terminal_send_mouse(term, user, x, y, mask); return 0; diff --git a/src/protocols/telnet/input.c b/src/protocols/telnet/input.c index be50360c..a6742e42 100644 --- a/src/protocols/telnet/input.c +++ b/src/protocols/telnet/input.c @@ -18,6 +18,7 @@ */ #include "config.h" +#include "common/recording.h" #include "input.h" #include "terminal/terminal.h" #include "telnet.h" @@ -42,6 +43,10 @@ int guac_telnet_user_mouse_handler(guac_user* user, int x, int y, int mask) { if (term == NULL) return 0; + /* Report mouse position within recording */ + if (telnet_client->recording != NULL) + guac_common_recording_report_mouse(telnet_client->recording, x, y); + /* Send mouse if not searching for password or username */ if (settings->password_regex == NULL && settings->username_regex == NULL) guac_terminal_send_mouse(term, user, x, y, mask); diff --git a/src/protocols/vnc/input.c b/src/protocols/vnc/input.c index 86f4ca76..ab88c43a 100644 --- a/src/protocols/vnc/input.c +++ b/src/protocols/vnc/input.c @@ -21,6 +21,7 @@ #include "common/cursor.h" #include "common/display.h" +#include "common/recording.h" #include "vnc.h" #include @@ -35,6 +36,10 @@ int guac_vnc_user_mouse_handler(guac_user* user, int x, int y, int mask) { /* Store current mouse location */ guac_common_cursor_move(vnc_client->display->cursor, user, x, y); + /* Report mouse position within recording */ + if (vnc_client->recording != NULL) + guac_common_recording_report_mouse(vnc_client->recording, x, y); + /* Send VNC event only if finished connecting */ if (rfb_client != NULL) SendPointerEvent(rfb_client, x, y, mask); From a14832c4daa933bdfd32df8985b223a77b381aa1 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 27 Nov 2017 14:21:06 -0800 Subject: [PATCH 3/8] GUACAMOLE-313: Implement the "cursor" instruction to guacenc. --- src/guacenc/Makefile.am | 2 + src/guacenc/cursor.c | 57 ++++++++++++++++++++++ src/guacenc/cursor.h | 83 ++++++++++++++++++++++++++++++++ src/guacenc/display-flatten.c | 46 +++++++++++++++++- src/guacenc/display.c | 7 +++ src/guacenc/display.h | 7 +++ src/guacenc/instruction-cursor.c | 36 ++++++++++---- 7 files changed, 228 insertions(+), 10 deletions(-) create mode 100644 src/guacenc/cursor.c create mode 100644 src/guacenc/cursor.h diff --git a/src/guacenc/Makefile.am b/src/guacenc/Makefile.am index b5039cbd..57fac56d 100644 --- a/src/guacenc/Makefile.am +++ b/src/guacenc/Makefile.am @@ -26,6 +26,7 @@ man_MANS = \ noinst_HEADERS = \ buffer.h \ + cursor.h \ display.h \ encode.h \ ffmpeg-compat.h \ @@ -41,6 +42,7 @@ noinst_HEADERS = \ guacenc_SOURCES = \ buffer.c \ + cursor.c \ display.c \ display-buffers.c \ display-image-streams.c \ diff --git a/src/guacenc/cursor.c b/src/guacenc/cursor.c new file mode 100644 index 00000000..4883ac10 --- /dev/null +++ b/src/guacenc/cursor.c @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" +#include "buffer.h" +#include "cursor.h" + +#include + +guacenc_cursor* guacenc_cursor_alloc() { + + /* Allocate new cursor */ + guacenc_cursor* cursor = (guacenc_cursor*) calloc(1, + sizeof(guacenc_cursor)); + if (cursor == NULL) + return NULL; + + /* Allocate associated buffer (image) */ + cursor->buffer = guacenc_buffer_alloc(); + if (cursor->buffer == NULL) { + free(cursor); + return NULL; + } + + return cursor; + +} + +void guacenc_cursor_free(guacenc_cursor* cursor) { + + /* Ignore NULL cursors */ + if (cursor == NULL) + return; + + /* Free underlying buffer */ + guacenc_buffer_free(cursor->buffer); + + free(cursor); + +} + diff --git a/src/guacenc/cursor.h b/src/guacenc/cursor.h new file mode 100644 index 00000000..73dc3c99 --- /dev/null +++ b/src/guacenc/cursor.h @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUACENC_CURSOR_H +#define GUACENC_CURSOR_H + +#include "config.h" +#include "buffer.h" + +#include +#include + +/** + * A mouse cursor, having a current location, hostspot, and associated cursor + * image. + */ +typedef struct guacenc_cursor { + + /** + * The current X coordinate of the mouse cursor, in pixels. + */ + int x; + + /** + * The current Y coordinate of the mouse cursor, in pixels. + */ + int y; + + /** + * The X coordinate of the mouse cursor hotspot within the cursor image, + * in pixels. + */ + int hotspot_x; + + /** + * The Y coordinate of the mouse cursor hotspot within the cursor image, + * in pixels. + */ + int hotspot_y; + + /** + * The current mouse cursor image. + */ + guacenc_buffer* buffer; + +} guacenc_cursor; + +/** + * Allocates and initializes a new cursor object. + * + * @return + * A newly-allocated and initialized guacenc_cursor, or NULL if allocation + * fails. + */ +guacenc_cursor* guacenc_cursor_alloc(); + +/** + * Frees all memory associated with the given cursor object. If the cursor + * provided is NULL, this function has no effect. + * + * @param cursor + * The cursor to free, which may be NULL. + */ +void guacenc_cursor_free(guacenc_cursor* cursor); + +#endif + diff --git a/src/guacenc/display-flatten.c b/src/guacenc/display-flatten.c index f081610c..e2fba829 100644 --- a/src/guacenc/display-flatten.c +++ b/src/guacenc/display-flatten.c @@ -20,9 +20,12 @@ #include "config.h" #include "display.h" #include "layer.h" +#include "log.h" #include +#include +#include #include #include @@ -75,6 +78,46 @@ static int guacenc_display_layer_comparator(const void* a, const void* b) { } +/** + * Renders the mouse cursor on top of the frame buffer of the default layer of + * the given display. + * + * @param display + * The display whose mouse cursor should be rendered to the frame buffer + * of its default layer. + * + * @return + * Zero if rendering succeeds, non-zero otherwise. + */ +static int guacenc_display_render_cursor(guacenc_display* display) { + + guacenc_cursor* cursor = display->cursor; + + /* Retrieve default layer (guaranteed to not be NULL) */ + guacenc_layer* def_layer = guacenc_display_get_layer(display, 0); + assert(def_layer != NULL); + + /* Get source and destination buffers */ + guacenc_buffer* src = cursor->buffer; + guacenc_buffer* dst = def_layer->frame; + + /* Render cursor to layer */ + if (src->width > 0 && src->height > 0) { + cairo_set_source_surface(dst->cairo, src->surface, + cursor->x - cursor->hotspot_x, + cursor->y - cursor->hotspot_y); + cairo_rectangle(dst->cairo, + cursor->x - cursor->hotspot_x, + cursor->y - cursor->hotspot_y, + src->width, src->height); + cairo_fill(dst->cairo); + } + + /* Always succeeds */ + return 0; + +} + int guacenc_display_flatten(guacenc_display* display) { int i; @@ -151,7 +194,8 @@ int guacenc_display_flatten(guacenc_display* display) { } - return 0; + /* Render cursor on top of everything else */ + return guacenc_display_render_cursor(display); } diff --git a/src/guacenc/display.c b/src/guacenc/display.c index fb0cb9aa..094376cf 100644 --- a/src/guacenc/display.c +++ b/src/guacenc/display.c @@ -18,6 +18,7 @@ */ #include "config.h" +#include "cursor.h" #include "display.h" #include "video.h" @@ -97,6 +98,9 @@ guacenc_display* guacenc_display_alloc(const char* path, const char* codec, /* Associate display with video output */ display->output = video; + /* Allocate special-purpose cursor layer */ + display->cursor = guacenc_cursor_alloc(); + return display; } @@ -124,6 +128,9 @@ int guacenc_display_free(guacenc_display* display) { for (i = 0; i < GUACENC_DISPLAY_MAX_STREAMS; i++) guacenc_image_stream_free(display->image_streams[i]); + /* Free cursor */ + guacenc_cursor_free(display->cursor); + free(display); return retval; diff --git a/src/guacenc/display.h b/src/guacenc/display.h index bb01d207..2616bdc3 100644 --- a/src/guacenc/display.h +++ b/src/guacenc/display.h @@ -22,10 +22,12 @@ #include "config.h" #include "buffer.h" +#include "cursor.h" #include "image-stream.h" #include "layer.h" #include "video.h" +#include #include #include @@ -52,6 +54,11 @@ */ typedef struct guacenc_display { + /** + * The current mouse cursor state. + */ + guacenc_cursor* cursor; + /** * All currently-allocated buffers. The index of the buffer corresponds to * its position within this array, where -1 is the 0th entry. If a buffer diff --git a/src/guacenc/instruction-cursor.c b/src/guacenc/instruction-cursor.c index 65fad91b..3307371a 100644 --- a/src/guacenc/instruction-cursor.c +++ b/src/guacenc/instruction-cursor.c @@ -18,6 +18,8 @@ */ #include "config.h" +#include "buffer.h" +#include "cursor.h" #include "display.h" #include "log.h" @@ -36,16 +38,32 @@ int guacenc_handle_cursor(guacenc_display* display, int argc, char** argv) { /* Parse arguments */ int hotspot_x = atoi(argv[0]); int hotspot_y = atoi(argv[1]); - int src_index = atoi(argv[2]); - int src_x = atoi(argv[3]); - int src_y = atoi(argv[4]); - int src_w = atoi(argv[5]); - int src_h = atoi(argv[6]); + int sindex = atoi(argv[2]); + int sx = atoi(argv[3]); + int sy = atoi(argv[4]); + int width = atoi(argv[5]); + int height = atoi(argv[6]); - /* Nothing to do with cursor (yet) */ - guacenc_log(GUAC_LOG_DEBUG, "Ignoring cursor: hotspot (%i, %i) " - "src_layer=%i (%i, %i) %ix%i", hotspot_x, hotspot_y, - src_index, src_x, src_y, src_w, src_h); + /* Pull buffer of source layer/buffer */ + guacenc_buffer* src = guacenc_display_get_related_buffer(display, sindex); + if (src == NULL) + return 1; + + /* Update cursor hotspot */ + guacenc_cursor* cursor = display->cursor; + cursor->hotspot_x = hotspot_x; + cursor->hotspot_y = hotspot_y; + + /* Resize cursor to exactly fit */ + guacenc_buffer_resize(cursor->buffer, width, height); + + /* Copy rectangle from source to cursor */ + guacenc_buffer* dst = cursor->buffer; + if (src->surface != NULL && dst->cairo != NULL) { + cairo_set_operator(dst->cairo, CAIRO_OPERATOR_SOURCE); + cairo_set_source_surface(dst->cairo, src->surface, sx, sy); + cairo_paint(dst->cairo); + } return 0; From cafcd90f9fd794d767a0883939436f5487d39a77 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 27 Nov 2017 14:24:58 -0800 Subject: [PATCH 4/8] GUACAMOLE-313: Add support for the "mouse" instruction to guacenc. --- src/guacenc/Makefile.am | 1 + src/guacenc/instruction-mouse.c | 49 +++++++++++++++++++++++++++++++++ src/guacenc/instructions.c | 1 + src/guacenc/instructions.h | 5 ++++ 4 files changed, 56 insertions(+) create mode 100644 src/guacenc/instruction-mouse.c diff --git a/src/guacenc/Makefile.am b/src/guacenc/Makefile.am index 57fac56d..a505ff5b 100644 --- a/src/guacenc/Makefile.am +++ b/src/guacenc/Makefile.am @@ -61,6 +61,7 @@ guacenc_SOURCES = \ instruction-dispose.c \ instruction-end.c \ instruction-img.c \ + instruction-mouse.c \ instruction-move.c \ instruction-rect.c \ instruction-shade.c \ diff --git a/src/guacenc/instruction-mouse.c b/src/guacenc/instruction-mouse.c new file mode 100644 index 00000000..ac9fd876 --- /dev/null +++ b/src/guacenc/instruction-mouse.c @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" +#include "cursor.h" +#include "display.h" +#include "log.h" + +#include + +#include + +int guacenc_handle_mouse(guacenc_display* display, int argc, char** argv) { + + /* Verify argument count */ + if (argc < 2) { + guacenc_log(GUAC_LOG_WARNING, "\"mouse\" instruction incomplete"); + return 1; + } + + /* Parse arguments */ + int x = atoi(argv[0]); + int y = atoi(argv[1]); + + /* Update cursor properties */ + guacenc_cursor* cursor = display->cursor; + cursor->x = x; + cursor->y = y; + + return 0; + +} + diff --git a/src/guacenc/instructions.c b/src/guacenc/instructions.c index d987c9bb..6ad4835a 100644 --- a/src/guacenc/instructions.c +++ b/src/guacenc/instructions.c @@ -30,6 +30,7 @@ guacenc_instruction_handler_mapping guacenc_instruction_handler_map[] = { {"blob", guacenc_handle_blob}, {"img", guacenc_handle_img}, {"end", guacenc_handle_end}, + {"mouse", guacenc_handle_mouse}, {"sync", guacenc_handle_sync}, {"cursor", guacenc_handle_cursor}, {"copy", guacenc_handle_copy}, diff --git a/src/guacenc/instructions.h b/src/guacenc/instructions.h index 5fb54165..b8dbdd41 100644 --- a/src/guacenc/instructions.h +++ b/src/guacenc/instructions.h @@ -114,6 +114,11 @@ guacenc_instruction_handler guacenc_handle_img; */ guacenc_instruction_handler guacenc_handle_end; +/** + * Handler for the Guacamole "mouse" instruction. + */ +guacenc_instruction_handler guacenc_handle_mouse; + /** * Handler for the Guacamole "sync" instruction. */ From e2455d6f26513c2d1e9bf7304c8b15436b58b9ef Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 27 Nov 2017 19:32:59 -0800 Subject: [PATCH 5/8] GUACAMOLE-313: Do not render cursor unless mouse has actually moved. --- src/guacenc/cursor.c | 6 ++++-- src/guacenc/cursor.h | 8 ++++++-- src/guacenc/display-flatten.c | 4 ++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/guacenc/cursor.c b/src/guacenc/cursor.c index 4883ac10..d67e9461 100644 --- a/src/guacenc/cursor.c +++ b/src/guacenc/cursor.c @@ -26,8 +26,7 @@ guacenc_cursor* guacenc_cursor_alloc() { /* Allocate new cursor */ - guacenc_cursor* cursor = (guacenc_cursor*) calloc(1, - sizeof(guacenc_cursor)); + guacenc_cursor* cursor = (guacenc_cursor*) malloc(sizeof(guacenc_cursor)); if (cursor == NULL) return NULL; @@ -38,6 +37,9 @@ guacenc_cursor* guacenc_cursor_alloc() { return NULL; } + /* Do not initially render cursor, unless it moves */ + cursor->x = cursor->y = -1; + return cursor; } diff --git a/src/guacenc/cursor.h b/src/guacenc/cursor.h index 73dc3c99..89c8fca0 100644 --- a/src/guacenc/cursor.h +++ b/src/guacenc/cursor.h @@ -33,12 +33,16 @@ typedef struct guacenc_cursor { /** - * The current X coordinate of the mouse cursor, in pixels. + * The current X coordinate of the mouse cursor, in pixels. Valid values + * are non-negative. Negative values indicate that the cursor should not + * be rendered. */ int x; /** - * The current Y coordinate of the mouse cursor, in pixels. + * The current Y coordinate of the mouse cursor, in pixels. Valid values + * are non-negative. Negative values indicate that the cursor should not + * be rendered. */ int y; diff --git a/src/guacenc/display-flatten.c b/src/guacenc/display-flatten.c index e2fba829..4a86c0d5 100644 --- a/src/guacenc/display-flatten.c +++ b/src/guacenc/display-flatten.c @@ -93,6 +93,10 @@ static int guacenc_display_render_cursor(guacenc_display* display) { guacenc_cursor* cursor = display->cursor; + /* Do not render cursor if coordinates are negative */ + if (cursor->x < 0 || cursor->y < 0) + return 0; + /* Retrieve default layer (guaranteed to not be NULL) */ guacenc_layer* def_layer = guacenc_display_get_layer(display, 0); assert(def_layer != NULL); From a74d6a2aaf40bcf997086b846c7b5904682852e7 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 27 Nov 2017 20:08:34 -0800 Subject: [PATCH 6/8] GUACAMOLE-313: Include timestamp with mouse position reporting. --- src/common/common/cursor.h | 6 ++++++ src/common/cursor.c | 11 +++++++++-- src/common/recording.c | 4 +++- src/libguac/guacamole/protocol.h | 7 ++++++- src/libguac/protocol.c | 5 ++++- 5 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/common/common/cursor.h b/src/common/common/cursor.h index 00bc48e7..30316f45 100644 --- a/src/common/common/cursor.h +++ b/src/common/common/cursor.h @@ -102,6 +102,12 @@ typedef struct guac_common_cursor { */ int y; + /** + * The server timestamp representing the point in time when the mousr + * location was last updated. + */ + guac_timestamp timestamp; + } guac_common_cursor; /** diff --git a/src/common/cursor.c b/src/common/cursor.c index f621b942..7f4fb8f6 100644 --- a/src/common/cursor.c +++ b/src/common/cursor.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -67,6 +68,7 @@ guac_common_cursor* guac_common_cursor_alloc(guac_client* client) { /* No user has moved the mouse yet */ cursor->user = NULL; + cursor->timestamp = guac_timestamp_current(); /* Start cursor in upper-left */ cursor->x = 0; @@ -101,7 +103,8 @@ void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user, guac_socket* socket) { /* Synchronize location */ - guac_protocol_send_mouse(socket, cursor->x, cursor->y); + guac_protocol_send_mouse(socket, cursor->x, cursor->y, + cursor->timestamp); /* Synchronize cursor image */ if (cursor->surface != NULL) { @@ -138,7 +141,8 @@ static void* guac_common_cursor_broadcast_position(guac_user* user, /* Send cursor position only if the user is not moving the cursor */ if (user != cursor->user) { - guac_protocol_send_mouse(user->socket, cursor->x, cursor->y); + guac_protocol_send_mouse(user->socket, cursor->x, cursor->y, + cursor->timestamp); guac_socket_flush(user->socket); } @@ -156,6 +160,9 @@ void guac_common_cursor_move(guac_common_cursor* cursor, guac_user* user, cursor->x = x; cursor->y = y; + /* Store time at which cursor position was updated */ + cursor->timestamp = guac_timestamp_current(); + /* Notify all other users of change in cursor position */ guac_client_foreach_user(cursor->client, guac_common_cursor_broadcast_position, cursor); diff --git a/src/common/recording.c b/src/common/recording.c index 4f334d5f..e7270d68 100644 --- a/src/common/recording.c +++ b/src/common/recording.c @@ -22,6 +22,7 @@ #include #include #include +#include #ifdef __MINGW32__ #include @@ -182,7 +183,8 @@ void guac_common_recording_report_mouse(guac_common_recording* recording, int x, int y) { /* Report mouse location */ - guac_protocol_send_mouse(recording->socket, x, y); + guac_protocol_send_mouse(recording->socket, x, y, + guac_timestamp_current()); } diff --git a/src/libguac/guacamole/protocol.h b/src/libguac/guacamole/protocol.h index 88a0a185..89ad6b28 100644 --- a/src/libguac/guacamole/protocol.h +++ b/src/libguac/guacamole/protocol.h @@ -159,10 +159,15 @@ int vguac_protocol_send_log(guac_socket* socket, const char* format, * @param y * The Y coordinate of the current mouse position. * + * @param timestamp + * The server timestamp (in milliseconds) at the point in time this mouse + * position was acknowledged. + * * @return * Zero on success, non-zero on error. */ -int guac_protocol_send_mouse(guac_socket* socket, int x, int y); +int guac_protocol_send_mouse(guac_socket* socket, int x, int y, + guac_timestamp timestamp); /** * Sends a nest instruction over the given guac_socket connection. diff --git a/src/libguac/protocol.c b/src/libguac/protocol.c index 8da18249..5e47b8c0 100644 --- a/src/libguac/protocol.c +++ b/src/libguac/protocol.c @@ -684,7 +684,8 @@ int guac_protocol_send_lstroke(guac_socket* socket, } -int guac_protocol_send_mouse(guac_socket* socket, int x, int y) { +int guac_protocol_send_mouse(guac_socket* socket, int x, int y, + guac_timestamp timestamp) { int ret_val; @@ -694,6 +695,8 @@ int guac_protocol_send_mouse(guac_socket* socket, int x, int y) { || __guac_socket_write_length_int(socket, x) || guac_socket_write_string(socket, ",") || __guac_socket_write_length_int(socket, y) + || guac_socket_write_string(socket, ",") + || __guac_socket_write_length_int(socket, timestamp) || guac_socket_write_string(socket, ";"); guac_socket_instruction_end(socket); From 7eb4e22515c2e3255e661bf26e27631940d5e231 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 27 Nov 2017 20:18:20 -0800 Subject: [PATCH 7/8] GUACAMOLE-313: Use mouse timestamps for frames as well as sync. --- src/guacenc/instruction-mouse.c | 9 ++++++++- src/guacenc/instruction-sync.c | 35 +-------------------------------- src/guacenc/parse.c | 23 ++++++++++++++++++++++ src/guacenc/parse.h | 17 ++++++++++++++++ 4 files changed, 49 insertions(+), 35 deletions(-) diff --git a/src/guacenc/instruction-mouse.c b/src/guacenc/instruction-mouse.c index ac9fd876..50952451 100644 --- a/src/guacenc/instruction-mouse.c +++ b/src/guacenc/instruction-mouse.c @@ -21,6 +21,7 @@ #include "cursor.h" #include "display.h" #include "log.h" +#include "parse.h" #include @@ -43,7 +44,13 @@ int guacenc_handle_mouse(guacenc_display* display, int argc, char** argv) { cursor->x = x; cursor->y = y; - return 0; + /* If no timestamp provided, nothing further to do */ + if (argc < 3) + return 0; + + /* Leverage timestamp to render frame */ + guac_timestamp timestamp = guacenc_parse_timestamp(argv[2]); + return guacenc_display_sync(display, timestamp); } diff --git a/src/guacenc/instruction-sync.c b/src/guacenc/instruction-sync.c index 5dd48444..c27ad145 100644 --- a/src/guacenc/instruction-sync.c +++ b/src/guacenc/instruction-sync.c @@ -20,6 +20,7 @@ #include "config.h" #include "display.h" #include "log.h" +#include "parse.h" #include #include @@ -27,40 +28,6 @@ #include #include -/** - * Parses a guac_timestamp from the given string. The string is assumed to - * consist solely of decimal digits with an optional leading minus sign. If the - * given string contains other characters, the behavior of this function is - * undefined. - * - * @param str - * The string to parse, which must contain only decimal digits and an - * optional leading minus sign. - * - * @return - * A guac_timestamp having the same value as the provided string. - */ -static guac_timestamp guacenc_parse_timestamp(const char* str) { - - int sign = 1; - int64_t num = 0; - - for (; *str != '\0'; str++) { - - /* Flip sign for each '-' encountered */ - if (*str == '-') - sign = -sign; - - /* If not '-', assume the character is a digit */ - else - num = num * 10 + (*str - '0'); - - } - - return (guac_timestamp) (num * sign); - -} - int guacenc_handle_sync(guacenc_display* display, int argc, char** argv) { /* Verify argument count */ diff --git a/src/guacenc/parse.c b/src/guacenc/parse.c index 0eea778e..8f05d38f 100644 --- a/src/guacenc/parse.c +++ b/src/guacenc/parse.c @@ -19,6 +19,8 @@ #include "config.h" +#include + #include #include #include @@ -67,3 +69,24 @@ int guacenc_parse_dimensions(char* arg, int* width, int* height) { } +guac_timestamp guacenc_parse_timestamp(const char* str) { + + int sign = 1; + int64_t num = 0; + + for (; *str != '\0'; str++) { + + /* Flip sign for each '-' encountered */ + if (*str == '-') + sign = -sign; + + /* If not '-', assume the character is a digit */ + else + num = num * 10 + (*str - '0'); + + } + + return (guac_timestamp) (num * sign); + +} + diff --git a/src/guacenc/parse.h b/src/guacenc/parse.h index f888913b..b6e5cd5a 100644 --- a/src/guacenc/parse.h +++ b/src/guacenc/parse.h @@ -22,6 +22,8 @@ #include "config.h" +#include + /** * Parses a string into a single integer. Only positive integers are accepted. * The input string may be modified during parsing. A value will be stored in @@ -63,6 +65,21 @@ int guacenc_parse_int(char* arg, int* i); */ int guacenc_parse_dimensions(char* arg, int* width, int* height); +/** + * Parses a guac_timestamp from the given string. The string is assumed to + * consist solely of decimal digits with an optional leading minus sign. If the + * given string contains other characters, the behavior of this function is + * undefined. + * + * @param str + * The string to parse, which must contain only decimal digits and an + * optional leading minus sign. + * + * @return + * A guac_timestamp having the same value as the provided string. + */ +guac_timestamp guacenc_parse_timestamp(const char* str); + #endif From 81a0e66d9f3dbd245adc8f4692ffa9e3175463f7 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 8 Dec 2017 10:45:09 -0800 Subject: [PATCH 8/8] GUACAMOLE-313: Include current button state within mouse update for completeness. --- src/common/common/cursor.h | 40 +++++++++++++++++++++++++++----- src/common/common/recording.h | 17 ++++++++++++-- src/common/cursor.c | 28 +++++++++++----------- src/common/recording.c | 4 ++-- src/guacenc/instruction-mouse.c | 4 ++-- src/libguac/guacamole/protocol.h | 15 +++++++++++- src/libguac/protocol.c | 4 +++- src/protocols/rdp/input.c | 6 ++--- src/protocols/ssh/input.c | 2 +- src/protocols/telnet/input.c | 3 ++- src/protocols/vnc/input.c | 6 ++--- src/terminal/terminal.c | 4 ++-- 12 files changed, 96 insertions(+), 37 deletions(-) diff --git a/src/common/common/cursor.h b/src/common/common/cursor.h index 30316f45..52d93c2a 100644 --- a/src/common/common/cursor.h +++ b/src/common/common/cursor.h @@ -102,6 +102,21 @@ typedef struct guac_common_cursor { */ int y; + /** + * An integer value representing the current state of each button, where + * the Nth bit within the integer is set to 1 if and only if the Nth mouse + * button is currently pressed. The lowest-order bit is the left mouse + * button, followed by the middle button, right button, and finally the up + * and down buttons of the scroll wheel. + * + * @see GUAC_CLIENT_MOUSE_LEFT + * @see GUAC_CLIENT_MOUSE_MIDDLE + * @see GUAC_CLIENT_MOUSE_RIGHT + * @see GUAC_CLIENT_MOUSE_SCROLL_UP + * @see GUAC_CLIENT_MOUSE_SCROLL_DOWN + */ + int button_mask; + /** * The server timestamp representing the point in time when the mousr * location was last updated. @@ -148,12 +163,12 @@ void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user, guac_socket* socket); /** - * Moves the mouse cursor, marking the given user as the most recent user of - * the mouse. The remote mouse cursor will be hidden for this user and shown - * for all others. + * Updates the current position and button state of the mouse cursor, marking + * the given user as the most recent user of the mouse. The remote mouse cursor + * will be hidden for this user and shown for all others. * * @param cursor - * The cursor being moved. + * The cursor being updated. * * @param user * The user that moved the cursor. @@ -163,9 +178,22 @@ void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user, * * @param y * The new Y coordinate of the cursor. + * + * @param button_mask + * An integer value representing the current state of each button, where + * the Nth bit within the integer is set to 1 if and only if the Nth mouse + * button is currently pressed. The lowest-order bit is the left mouse + * button, followed by the middle button, right button, and finally the up + * and down buttons of the scroll wheel. + * + * @see GUAC_CLIENT_MOUSE_LEFT + * @see GUAC_CLIENT_MOUSE_MIDDLE + * @see GUAC_CLIENT_MOUSE_RIGHT + * @see GUAC_CLIENT_MOUSE_SCROLL_UP + * @see GUAC_CLIENT_MOUSE_SCROLL_DOWN */ -void guac_common_cursor_move(guac_common_cursor* cursor, guac_user* user, - int x, int y); +void guac_common_cursor_update(guac_common_cursor* cursor, guac_user* user, + int x, int y, int button_mask); /** * Sets the cursor image to the given raw image data. This raw image data must diff --git a/src/common/common/recording.h b/src/common/common/recording.h index e03e7881..03d42574 100644 --- a/src/common/common/recording.h +++ b/src/common/common/recording.h @@ -102,7 +102,7 @@ guac_common_recording* guac_common_recording_create(guac_client* client, void guac_common_recording_free(guac_common_recording* recording); /** - * Reports the current mouse position within the recording. + * Reports the current mouse position and button state within the recording. * * @param recording * The guac_common_recording associated with the mouse that has moved. @@ -112,9 +112,22 @@ void guac_common_recording_free(guac_common_recording* recording); * * @param y * The new Y coordinate of the mouse cursor, in pixels. + * + * @param button_mask + * An integer value representing the current state of each button, where + * the Nth bit within the integer is set to 1 if and only if the Nth mouse + * button is currently pressed. The lowest-order bit is the left mouse + * button, followed by the middle button, right button, and finally the up + * and down buttons of the scroll wheel. + * + * @see GUAC_CLIENT_MOUSE_LEFT + * @see GUAC_CLIENT_MOUSE_MIDDLE + * @see GUAC_CLIENT_MOUSE_RIGHT + * @see GUAC_CLIENT_MOUSE_SCROLL_UP + * @see GUAC_CLIENT_MOUSE_SCROLL_DOWN */ void guac_common_recording_report_mouse(guac_common_recording* recording, - int x, int y); + int x, int y, int button_mask); #endif diff --git a/src/common/cursor.c b/src/common/cursor.c index 7f4fb8f6..b70134d8 100644 --- a/src/common/cursor.c +++ b/src/common/cursor.c @@ -103,7 +103,7 @@ void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user, guac_socket* socket) { /* Synchronize location */ - guac_protocol_send_mouse(socket, cursor->x, cursor->y, + guac_protocol_send_mouse(socket, cursor->x, cursor->y, cursor->button_mask, cursor->timestamp); /* Synchronize cursor image */ @@ -125,24 +125,25 @@ void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user, /** * Callback for guac_client_foreach_user() which sends the current cursor - * position to any given user except the user that moved the cursor last. + * position and button state to any given user except the user that moved the + * cursor last. * * @param data - * A pointer to the guac_common_cursor whose position should be broadcast - * to all users except the user that moved the cursor last. + * A pointer to the guac_common_cursor whose state should be broadcast to + * all users except the user that moved the cursor last. * * @return * Always NULL. */ -static void* guac_common_cursor_broadcast_position(guac_user* user, +static void* guac_common_cursor_broadcast_state(guac_user* user, void* data) { guac_common_cursor* cursor = (guac_common_cursor*) data; - /* Send cursor position only if the user is not moving the cursor */ + /* Send cursor state only if the user is not moving the cursor */ if (user != cursor->user) { guac_protocol_send_mouse(user->socket, cursor->x, cursor->y, - cursor->timestamp); + cursor->button_mask, cursor->timestamp); guac_socket_flush(user->socket); } @@ -150,22 +151,23 @@ static void* guac_common_cursor_broadcast_position(guac_user* user, } -void guac_common_cursor_move(guac_common_cursor* cursor, guac_user* user, - int x, int y) { +void guac_common_cursor_update(guac_common_cursor* cursor, guac_user* user, + int x, int y, int button_mask) { /* Update current user of cursor */ cursor->user = user; - /* Update cursor position */ + /* Update cursor state */ cursor->x = x; cursor->y = y; + cursor->button_mask = button_mask; - /* Store time at which cursor position was updated */ + /* Store time at which cursor was updated */ cursor->timestamp = guac_timestamp_current(); - /* Notify all other users of change in cursor position */ + /* Notify all other users of change in cursor state */ guac_client_foreach_user(cursor->client, - guac_common_cursor_broadcast_position, cursor); + guac_common_cursor_broadcast_state, cursor); } diff --git a/src/common/recording.c b/src/common/recording.c index e7270d68..875dcc3e 100644 --- a/src/common/recording.c +++ b/src/common/recording.c @@ -180,10 +180,10 @@ void guac_common_recording_free(guac_common_recording* recording) { } void guac_common_recording_report_mouse(guac_common_recording* recording, - int x, int y) { + int x, int y, int button_mask) { /* Report mouse location */ - guac_protocol_send_mouse(recording->socket, x, y, + guac_protocol_send_mouse(recording->socket, x, y, button_mask, guac_timestamp_current()); } diff --git a/src/guacenc/instruction-mouse.c b/src/guacenc/instruction-mouse.c index 50952451..9b9c526a 100644 --- a/src/guacenc/instruction-mouse.c +++ b/src/guacenc/instruction-mouse.c @@ -45,11 +45,11 @@ int guacenc_handle_mouse(guacenc_display* display, int argc, char** argv) { cursor->y = y; /* If no timestamp provided, nothing further to do */ - if (argc < 3) + if (argc < 4) return 0; /* Leverage timestamp to render frame */ - guac_timestamp timestamp = guacenc_parse_timestamp(argv[2]); + guac_timestamp timestamp = guacenc_parse_timestamp(argv[3]); return guacenc_display_sync(display, timestamp); } diff --git a/src/libguac/guacamole/protocol.h b/src/libguac/guacamole/protocol.h index 89ad6b28..a3e6c181 100644 --- a/src/libguac/guacamole/protocol.h +++ b/src/libguac/guacamole/protocol.h @@ -159,6 +159,19 @@ int vguac_protocol_send_log(guac_socket* socket, const char* format, * @param y * The Y coordinate of the current mouse position. * + * @param button_mask + * An integer value representing the current state of each button, where + * the Nth bit within the integer is set to 1 if and only if the Nth mouse + * button is currently pressed. The lowest-order bit is the left mouse + * button, followed by the middle button, right button, and finally the up + * and down buttons of the scroll wheel. + * + * @see GUAC_CLIENT_MOUSE_LEFT + * @see GUAC_CLIENT_MOUSE_MIDDLE + * @see GUAC_CLIENT_MOUSE_RIGHT + * @see GUAC_CLIENT_MOUSE_SCROLL_UP + * @see GUAC_CLIENT_MOUSE_SCROLL_DOWN + * * @param timestamp * The server timestamp (in milliseconds) at the point in time this mouse * position was acknowledged. @@ -167,7 +180,7 @@ int vguac_protocol_send_log(guac_socket* socket, const char* format, * Zero on success, non-zero on error. */ int guac_protocol_send_mouse(guac_socket* socket, int x, int y, - guac_timestamp timestamp); + int button_mask, guac_timestamp timestamp); /** * Sends a nest instruction over the given guac_socket connection. diff --git a/src/libguac/protocol.c b/src/libguac/protocol.c index 5e47b8c0..a5fe3e9a 100644 --- a/src/libguac/protocol.c +++ b/src/libguac/protocol.c @@ -685,7 +685,7 @@ int guac_protocol_send_lstroke(guac_socket* socket, } int guac_protocol_send_mouse(guac_socket* socket, int x, int y, - guac_timestamp timestamp) { + int button_mask, guac_timestamp timestamp) { int ret_val; @@ -696,6 +696,8 @@ int guac_protocol_send_mouse(guac_socket* socket, int x, int y, || guac_socket_write_string(socket, ",") || __guac_socket_write_length_int(socket, y) || guac_socket_write_string(socket, ",") + || __guac_socket_write_length_int(socket, button_mask) + || guac_socket_write_string(socket, ",") || __guac_socket_write_length_int(socket, timestamp) || guac_socket_write_string(socket, ";"); diff --git a/src/protocols/rdp/input.c b/src/protocols/rdp/input.c index 7cb3da8f..df9c5439 100644 --- a/src/protocols/rdp/input.c +++ b/src/protocols/rdp/input.c @@ -47,12 +47,12 @@ int guac_rdp_user_mouse_handler(guac_user* user, int x, int y, int mask) { return 0; } - /* Store current mouse location */ - guac_common_cursor_move(rdp_client->display->cursor, user, x, y); + /* Store current mouse location/state */ + guac_common_cursor_update(rdp_client->display->cursor, user, x, y, mask); /* Report mouse position within recording */ if (rdp_client->recording != NULL) - guac_common_recording_report_mouse(rdp_client->recording, x, y); + guac_common_recording_report_mouse(rdp_client->recording, x, y, mask); /* If button mask unchanged, just send move event */ if (mask == rdp_client->mouse_button_mask) diff --git a/src/protocols/ssh/input.c b/src/protocols/ssh/input.c index b4d0cf5c..154c9ab4 100644 --- a/src/protocols/ssh/input.c +++ b/src/protocols/ssh/input.c @@ -43,7 +43,7 @@ int guac_ssh_user_mouse_handler(guac_user* user, int x, int y, int mask) { /* Report mouse position within recording */ if (ssh_client->recording != NULL) - guac_common_recording_report_mouse(ssh_client->recording, x, y); + guac_common_recording_report_mouse(ssh_client->recording, x, y, mask); /* Send mouse event */ guac_terminal_send_mouse(term, user, x, y, mask); diff --git a/src/protocols/telnet/input.c b/src/protocols/telnet/input.c index a6742e42..de551dd0 100644 --- a/src/protocols/telnet/input.c +++ b/src/protocols/telnet/input.c @@ -45,7 +45,8 @@ int guac_telnet_user_mouse_handler(guac_user* user, int x, int y, int mask) { /* Report mouse position within recording */ if (telnet_client->recording != NULL) - guac_common_recording_report_mouse(telnet_client->recording, x, y); + guac_common_recording_report_mouse(telnet_client->recording, x, y, + mask); /* Send mouse if not searching for password or username */ if (settings->password_regex == NULL && settings->username_regex == NULL) diff --git a/src/protocols/vnc/input.c b/src/protocols/vnc/input.c index ab88c43a..1dec1c99 100644 --- a/src/protocols/vnc/input.c +++ b/src/protocols/vnc/input.c @@ -33,12 +33,12 @@ int guac_vnc_user_mouse_handler(guac_user* user, int x, int y, int mask) { guac_vnc_client* vnc_client = (guac_vnc_client*) client->data; rfbClient* rfb_client = vnc_client->rfb_client; - /* Store current mouse location */ - guac_common_cursor_move(vnc_client->display->cursor, user, x, y); + /* Store current mouse location/state */ + guac_common_cursor_update(vnc_client->display->cursor, user, x, y, mask); /* Report mouse position within recording */ if (vnc_client->recording != NULL) - guac_common_recording_report_mouse(vnc_client->recording, x, y); + guac_common_recording_report_mouse(vnc_client->recording, x, y, mask); /* Send VNC event only if finished connecting */ if (rfb_client != NULL) diff --git a/src/terminal/terminal.c b/src/terminal/terminal.c index a253572b..4d2171a0 100644 --- a/src/terminal/terminal.c +++ b/src/terminal/terminal.c @@ -1670,8 +1670,8 @@ static int __guac_terminal_send_mouse(guac_terminal* term, guac_user* user, int released_mask = term->mouse_mask & ~mask; int pressed_mask = ~term->mouse_mask & mask; - /* Store current mouse location */ - guac_common_cursor_move(term->cursor, user, x, y); + /* Store current mouse location/state */ + guac_common_cursor_update(term->cursor, user, x, y, mask); /* Notify scrollbar, do not handle anything handled by scrollbar */ if (guac_terminal_scrollbar_handle_mouse(term->scrollbar, x, y, mask)) {