From 1f0dc9e77abb9a0478a1293c86ff1602d11f38eb Mon Sep 17 00:00:00 2001 From: James Muehlner Date: Tue, 22 Aug 2023 19:11:53 +0000 Subject: [PATCH] GUACAMOLE-1846: Sync data to all pending users using broadcast socket. --- src/common/common/cursor.h | 6 +- src/common/common/display.h | 7 +- src/common/common/surface.h | 8 +-- src/common/cursor.c | 6 +- src/common/display.c | 21 +++--- src/common/surface.c | 7 +- src/libguac/client.c | 8 ++- src/libguac/guacamole/client-fntypes.h | 8 ++- src/libguac/guacamole/client.h | 26 +++++--- src/libguac/guacamole/socket.h | 36 +++++++++-- src/libguac/socket-broadcast.c | 90 ++++++++++++++++++++------ src/protocols/kubernetes/argv.c | 16 +++-- src/protocols/kubernetes/argv.h | 21 +++++- src/protocols/kubernetes/client.c | 40 +++--------- src/protocols/rdp/channels/pipe-svc.c | 8 +-- src/protocols/rdp/channels/pipe-svc.h | 16 +++-- src/protocols/rdp/client.c | 42 ++++++------ src/protocols/ssh/argv.c | 19 ++++-- src/protocols/ssh/argv.h | 18 ++++++ src/protocols/ssh/client.c | 34 ++-------- src/protocols/telnet/argv.c | 20 ++++-- src/protocols/telnet/argv.h | 19 ++++++ src/protocols/telnet/client.c | 35 ++-------- src/protocols/telnet/user.c | 7 -- src/protocols/vnc/client.c | 40 ++++++------ src/protocols/vnc/user.c | 15 ----- src/terminal/display.c | 12 +++- src/terminal/scrollbar.c | 13 +++- src/terminal/terminal.c | 41 ++++++++++-- src/terminal/terminal/display.h | 19 ++++++ src/terminal/terminal/scrollbar.h | 22 +++++++ src/terminal/terminal/terminal.h | 21 ++++++ 32 files changed, 453 insertions(+), 248 deletions(-) diff --git a/src/common/common/cursor.h b/src/common/common/cursor.h index 52d93c2ae8..208f61f3ae 100644 --- a/src/common/common/cursor.h +++ b/src/common/common/cursor.h @@ -153,14 +153,14 @@ void guac_common_cursor_free(guac_common_cursor* cursor); * @param cursor * The cursor to send. * - * @param user + * @param client * The user receiving the updated cursor. * * @param socket * The socket over which the updated cursor should be sent. */ -void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user, - guac_socket* socket); +void guac_common_cursor_dup( + guac_common_cursor* cursor, guac_client* client, guac_socket* socket); /** * Updates the current position and button state of the mouse cursor, marking diff --git a/src/common/common/display.h b/src/common/common/display.h index c54289021e..33950a1775 100644 --- a/src/common/common/display.h +++ b/src/common/common/display.h @@ -151,13 +151,14 @@ void guac_common_display_free(guac_common_display* display); * @param display * The display whose state should be sent along the given socket. * - * @param user - * The user receiving the display state. + * @param client + * The client associated with the users receiving the display state. * * @param socket * The socket over which the display state should be sent. */ -void guac_common_display_dup(guac_common_display* display, guac_user* user, +void guac_common_display_dup( + guac_common_display* display, guac_client* client, guac_socket* socket); /** diff --git a/src/common/common/surface.h b/src/common/common/surface.h index b43dcaaf5a..09e5b0d5b3 100644 --- a/src/common/common/surface.h +++ b/src/common/common/surface.h @@ -490,14 +490,14 @@ void guac_common_surface_flush(guac_common_surface* surface); * @param surface * The surface to duplicate. * - * @param user - * The user receiving the surface. + * @param client + * The client whos users are receiving the surface. * * @param socket * The socket over which the surface contents should be sent. */ -void guac_common_surface_dup(guac_common_surface* surface, guac_user* user, - guac_socket* socket); +void guac_common_surface_dup(guac_common_surface* surface, + guac_client* client, guac_socket* socket); /** * Declares that the given surface should receive touch events. By default, diff --git a/src/common/cursor.c b/src/common/cursor.c index b70134d81e..78c4dcf1ef 100644 --- a/src/common/cursor.c +++ b/src/common/cursor.c @@ -99,8 +99,8 @@ void guac_common_cursor_free(guac_common_cursor* cursor) { } -void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user, - guac_socket* socket) { +void guac_common_cursor_dup( + guac_common_cursor* cursor, guac_client* client, guac_socket* socket) { /* Synchronize location */ guac_protocol_send_mouse(socket, cursor->x, cursor->y, cursor->button_mask, @@ -111,7 +111,7 @@ void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user, guac_protocol_send_size(socket, cursor->buffer, cursor->width, cursor->height); - guac_user_stream_png(user, socket, GUAC_COMP_SRC, + guac_client_stream_png(client, socket, GUAC_COMP_SRC, cursor->buffer, 0, 0, cursor->surface); guac_protocol_send_cursor(socket, diff --git a/src/common/display.c b/src/common/display.c index e8465e443d..73177bf748 100644 --- a/src/common/display.c +++ b/src/common/display.c @@ -37,20 +37,20 @@ * The head element of the linked list of layers to synchronize, which may * be NULL if the list is currently empty. * - * @param user - * The user receiving the layers. + * @param client + * The client associated with the users receiving the layers. * * @param socket * The socket over which each layer should be sent. */ static void guac_common_display_dup_layers(guac_common_display_layer* layers, - guac_user* user, guac_socket* socket) { + guac_client* client, guac_socket* socket) { guac_common_display_layer* current = layers; /* Synchronize all surfaces in given list */ while (current != NULL) { - guac_common_surface_dup(current->surface, user, socket); + guac_common_surface_dup(current->surface, client, socket); current = current->next; } @@ -163,22 +163,21 @@ void guac_common_display_free(guac_common_display* display) { } -void guac_common_display_dup(guac_common_display* display, guac_user* user, +void guac_common_display_dup( + guac_common_display* display, guac_client* client, guac_socket* socket) { - guac_client* client = user->client; - pthread_mutex_lock(&display->_lock); /* Sunchronize shared cursor */ - guac_common_cursor_dup(display->cursor, user, socket); + guac_common_cursor_dup(display->cursor, client, socket); /* Synchronize default surface */ - guac_common_surface_dup(display->default_surface, user, socket); + guac_common_surface_dup(display->default_surface, client, socket); /* Synchronize all layers and buffers */ - guac_common_display_dup_layers(display->layers, user, socket); - guac_common_display_dup_layers(display->buffers, user, socket); + guac_common_display_dup_layers(display->layers, client, socket); + guac_common_display_dup_layers(display->buffers, client, socket); /* Sends a sync instruction to mark the boundary of the first frame */ guac_protocol_send_sync(socket, client->last_sent_timestamp, 1); diff --git a/src/common/surface.c b/src/common/surface.c index 61b77c7d2e..e5d1ca9f33 100644 --- a/src/common/surface.c +++ b/src/common/surface.c @@ -1989,8 +1989,8 @@ void guac_common_surface_flush(guac_common_surface* surface) { } -void guac_common_surface_dup(guac_common_surface* surface, guac_user* user, - guac_socket* socket) { +void guac_common_surface_dup(guac_common_surface* surface, + guac_client* client, guac_socket* socket) { pthread_mutex_lock(&surface->_lock); @@ -2028,7 +2028,7 @@ void guac_common_surface_dup(guac_common_surface* surface, guac_user* user, surface->width, surface->height, surface->stride); /* Send PNG for rect */ - guac_user_stream_png(user, socket, GUAC_COMP_OVER, surface->layer, + guac_client_stream_png(client, socket, GUAC_COMP_OVER, surface->layer, 0, 0, rect); cairo_surface_destroy(rect); @@ -2038,4 +2038,3 @@ void guac_common_surface_dup(guac_common_surface* surface, guac_user* user, pthread_mutex_unlock(&surface->_lock); } - diff --git a/src/libguac/client.c b/src/libguac/client.c index ffa95c74af..46a0118cfc 100644 --- a/src/libguac/client.c +++ b/src/libguac/client.c @@ -193,7 +193,7 @@ static void guac_client_promote_pending_users(union sigval data) { guac_release_lock(&(client->__users_lock)); - /* Release the lock (this is done AFTER updating the non-pending user list + /* Release the lock (this is done AFTER updating the connected user list * to ensure that all users are always on exactly one of these lists) */ guac_release_lock(&(client->__pending_users_lock)); @@ -253,8 +253,9 @@ guac_client* guac_client_alloc() { /* Ensure the timer is constructed only once */ pthread_mutex_init(&(client->__pending_users_timer_mutex), NULL); - /* Set up socket to broadcast to all users */ + /* Set up broadcast sockets */ client->socket = guac_socket_broadcast(client); + client->pending_socket = guac_socket_broadcast_pending(client); /* Set the timer event thread as initially inactive, since it hasn't run */ atomic_flag_clear(&(client->__pending_timer_event_active)); @@ -280,8 +281,9 @@ void guac_client_free(guac_client* client) { } - /* Free socket */ + /* Free sockets */ guac_socket_free(client->socket); + guac_socket_free(client->pending_socket); /* Free layer pools */ guac_pool_free(client->__buffer_pool); diff --git a/src/libguac/guacamole/client-fntypes.h b/src/libguac/guacamole/client-fntypes.h index 3cf76f938e..26cddb6f8f 100644 --- a/src/libguac/guacamole/client-fntypes.h +++ b/src/libguac/guacamole/client-fntypes.h @@ -30,7 +30,9 @@ #include "client-types.h" #include "object-types.h" #include "protocol-types.h" +#include "socket.h" #include "stream-types.h" +#include "user-fntypes.h" #include "user-types.h" #include @@ -49,9 +51,9 @@ typedef int guac_client_free_handler(guac_client* client); /** - * Handler that will run before pending users are promoted to full users. - * Any required operations for pending users should be applied using - * guac_client_foreach_pending_user(). + * Handler that will run before immediately before pending users are promoted + * to full users. The pending user socket should be used to communicate to the + * pending users. * * @param client * The client whose handler was invoked. diff --git a/src/libguac/guacamole/client.h b/src/libguac/guacamole/client.h index 65947d4711..a8552cb9e1 100644 --- a/src/libguac/guacamole/client.h +++ b/src/libguac/guacamole/client.h @@ -49,16 +49,24 @@ struct guac_client { /** - * The guac_socket structure to be used to communicate with all connected - * web-clients (users). Unlike the user-level guac_socket, this guac_socket - * will broadcast instructions to all connected users simultaneously. It - * is expected that the implementor of any Guacamole proxy client will - * provide their own mechanism of I/O for their protocol. The guac_socket - * structure is used only to communicate conveniently with the Guacamole - * web-client. + * The guac_socket structure to be used to communicate with all non-pending + * connected web-clients (users). Unlike the user-level guac_socket, this + * guac_socket will broadcast instructions to all non-pending connected users + * simultaneously. It is expected that the implementor of any Guacamole proxy + * client will provide their own mechanism of I/O for their protocol. The + * guac_socket structure is used only to communicate conveniently with the + * Guacamole web-client. */ guac_socket* socket; + /** + * The guac_socket structure to be used to communicate with all pending + * connected web-clients (users). Aside from operating on a different + * subset of users, this socket has all the same behavior and semantics as + * the non-pending socket. + */ + guac_socket* pending_socket; + /** * The current state of the client. When the client is first allocated, * this will be initialized to GUAC_CLIENT_RUNNING. It will remain at @@ -248,8 +256,8 @@ struct guac_client { /** * A handler that will be run prior to pending users being promoted to full - * users. Any required pending user operations should be applied - * guac_client_foreach_pending_user(). + * users. Any required pending user operations should be performed using + * the client's pending user socket. * * Example: * @code diff --git a/src/libguac/guacamole/socket.h b/src/libguac/guacamole/socket.h index e7afade3b7..62aa78154a 100644 --- a/src/libguac/guacamole/socket.h +++ b/src/libguac/guacamole/socket.h @@ -235,10 +235,10 @@ guac_socket* guac_socket_tee(guac_socket* primary, guac_socket* secondary); /** * Allocates and initializes a new guac_socket which duplicates all - * instructions written across the sockets of each connected user of the given - * guac_client. The returned socket is a write-only socket. Attempts to read - * from the socket will fail. If a write occurs while no users are connected, - * that write will simply be dropped. + * instructions written across the sockets of each connected user of the + * given guac_client. The returned socket is a write-only socket. Attempts + * to read from the socket will fail. If a write occurs while no users are + * connected, that write will simply be dropped. * * Return values (error codes) from each user's socket will not affect the * in-progress write, but each failing user will be forcibly stopped with @@ -253,12 +253,38 @@ guac_socket* guac_socket_tee(guac_socket* primary, guac_socket* secondary); * * @return * A write-only guac_socket object which broadcasts copies of all - * instructions written across all connected users of the given + * instructions written across all non-pending connected users of the given * guac_client, or NULL if an error occurs while allocating the guac_socket * object. */ guac_socket* guac_socket_broadcast(guac_client* client); +/** + * Allocates and initializes a new guac_socket which duplicates all + * instructions written across the sockets of each pending connected + * user of the given guac_client. The returned socket is a write-only socket. + * Attempts to read from the socket will fail. If a write occurs while no + * users are connected, that write will simply be dropped. + * + * Return values (error codes) from each user's socket will not affect the + * in-progress write, but each failing user will be forcibly stopped with + * guac_user_stop(). + * + * If an error occurs while allocating the guac_socket object, NULL is returned, + * and guac_error is set appropriately. + * + * @param client + * The client associated with the group of pending users across which + * duplicates of all instructions should be written. + * + * @return + * A write-only guac_socket object which broadcasts copies of all + * instructions written across all pending connected users of the given + * guac_client, or NULL if an error occurs while allocating the guac_socket + * object. + */ +guac_socket* guac_socket_broadcast_pending(guac_client* client); + /** * Writes the given unsigned int to the given guac_socket object. The data * written may be buffered until the buffer is flushed automatically or diff --git a/src/libguac/socket-broadcast.c b/src/libguac/socket-broadcast.c index f551e81721..49f6672ed9 100644 --- a/src/libguac/socket-broadcast.c +++ b/src/libguac/socket-broadcast.c @@ -28,8 +28,25 @@ #include /** - * Data associated with an open socket which writes to all connected users of - * a particular guac_client. + * A function that will broadcast arbitrary data to a subset of users for + * the provided client, using the provided user callback for any user-specific + * operations. + * + * @param client + * The guac_client associated with the users to broadcast to. + * + * @param callback + * A callback that should be invoked with each broadcasted user. + * + * @param data + * Arbitrary data that may be used to broadcast to the subset of users. + */ +typedef void guac_socket_broadcast_handler( + guac_client* client, guac_user_callback* callback, void* data); + +/** + * Data associated with an open socket which writes to a subset of connected + * users of a particular guac_client. */ typedef struct guac_socket_broadcast_data { @@ -45,6 +62,11 @@ typedef struct guac_socket_broadcast_data { */ pthread_mutex_t socket_lock; + /** + * The function to broadcast + */ + guac_socket_broadcast_handler* broadcast_handler; + } guac_socket_broadcast_data; /** @@ -91,7 +113,7 @@ static ssize_t __guac_socket_broadcast_read_handler(guac_socket* socket, } /** - * Callback invoked by guac_client_foreach_user() which write a given chunk of + * Callback invoked by the broadcast handler which write a given chunk of * data to that user's socket. If the write attempt fails, the user is * signalled to stop with guac_user_stop(). * @@ -146,15 +168,15 @@ static ssize_t __guac_socket_broadcast_write_handler(guac_socket* socket, chunk.buffer = buf; chunk.length = count; - /* Broadcast chunk to all users */ - guac_client_foreach_user(data->client, __write_chunk_callback, &chunk); + /* Broadcast chunk to the users */ + data->broadcast_handler(data->client, __write_chunk_callback, &chunk); return count; } /** - * Callback which is invoked by guac_client_foreach_user() to flush all + * Callback which is invoked by the broadcast handler to flush all * pending data on the given user's socket. If an error occurs while flushing * a user's socket, that user is signalled to stop with guac_user_stop(). * @@ -162,7 +184,7 @@ static ssize_t __guac_socket_broadcast_write_handler(guac_socket* socket, * The user whose socket should be flushed. * * @param data - * Arbitrary data passed to guac_client_foreach_user(). This is not needed + * Arbitrary data passed to the broadcast handler. This is not needed * by this callback, and should be left as NULL. * * @return @@ -195,15 +217,15 @@ static ssize_t __guac_socket_broadcast_flush_handler(guac_socket* socket) { guac_socket_broadcast_data* data = (guac_socket_broadcast_data*) socket->data; - /* Flush all users */ - guac_client_foreach_user(data->client, __flush_callback, NULL); + /* Flush the users */ + data->broadcast_handler(data->client, __flush_callback, NULL); return 0; } /** - * Callback which is invoked by guac_client_foreach_user() to lock the given + * Callback which is invoked by the broadcast handler to lock the given * user's socket in preparation for the beginning of a Guacamole protocol * instruction. * @@ -211,7 +233,7 @@ static ssize_t __guac_socket_broadcast_flush_handler(guac_socket* socket) { * The user whose socket should be locked. * * @param data - * Arbitrary data passed to guac_client_foreach_user(). This is not needed + * Arbitrary data passed to the broadcast handler. This is not needed * by this callback, and should be left as NULL. * * @return @@ -243,20 +265,20 @@ static void __guac_socket_broadcast_lock_handler(guac_socket* socket) { /* Acquire exclusive access to socket */ pthread_mutex_lock(&(data->socket_lock)); - /* Lock sockets of all users */ - guac_client_foreach_user(data->client, __lock_callback, NULL); + /* Lock sockets of the users */ + data->broadcast_handler(data->client, __lock_callback, NULL); } /** - * Callback which is invoked by guac_client_foreach_user() to unlock the given + * Callback which is invoked by the broadcast handler to unlock the given * user's socket at the end of a Guacamole protocol instruction. * * @param user * The user whose socket should be unlocked. * * @param data - * Arbitrary data passed to guac_client_foreach_user(). This is not needed + * Arbitrary data passed to the broadcast handler. This is not needed * by this callback, and should be left as NULL. * * @return @@ -285,7 +307,7 @@ static void __guac_socket_broadcast_unlock_handler(guac_socket* socket) { (guac_socket_broadcast_data*) socket->data; /* Unlock sockets of all users */ - guac_client_foreach_user(data->client, __unlock_callback, NULL); + data->broadcast_handler(data->client, __unlock_callback, NULL); /* Relinquish exclusive access to socket */ pthread_mutex_unlock(&(data->socket_lock)); @@ -343,7 +365,22 @@ static int __guac_socket_broadcast_free_handler(guac_socket* socket) { } -guac_socket* guac_socket_broadcast(guac_client* client) { +/** + * Construct and return a socket that will broadcast to the users given by + * by the provided broadcast handler. + * + * @param client + * The client who's users are being broadcast to. + * + * @param broadcast_handler + * The handler that will peform the broadcast against a subset of users + * of the provided client. + * + * @return + * The newly constructed broadcast socket + */ +static guac_socket* __guac_socket_init( + guac_client* client, guac_socket_broadcast_handler* broadcast_handler) { pthread_mutexattr_t lock_attributes; @@ -352,6 +389,9 @@ guac_socket* guac_socket_broadcast(guac_client* client) { guac_socket_broadcast_data* data = malloc(sizeof(guac_socket_broadcast_data)); + /* Set the provided broadcast handler */ + data->broadcast_handler = broadcast_handler; + /* Store client as socket data */ data->client = client; socket->data = data; @@ -361,7 +401,7 @@ guac_socket* guac_socket_broadcast(guac_client* client) { /* Init lock */ pthread_mutex_init(&(data->socket_lock), &lock_attributes); - + /* Set read/write handlers */ socket->read_handler = __guac_socket_broadcast_read_handler; socket->write_handler = __guac_socket_broadcast_write_handler; @@ -375,3 +415,17 @@ guac_socket* guac_socket_broadcast(guac_client* client) { } +guac_socket* guac_socket_broadcast(guac_client* client) { + + /* Broadcast to all connected non-pending users*/ + return __guac_socket_init(client, guac_client_foreach_user); + +} + +guac_socket* guac_socket_broadcast_pending(guac_client* client) { + + /* Broadcast to all connected pending users*/ + return __guac_socket_init(client, guac_client_foreach_pending_user); + +} + diff --git a/src/protocols/kubernetes/argv.c b/src/protocols/kubernetes/argv.c index 0cbc71601d..9b2bfcfcd8 100644 --- a/src/protocols/kubernetes/argv.c +++ b/src/protocols/kubernetes/argv.c @@ -63,23 +63,31 @@ int guac_kubernetes_argv_callback(guac_user* user, const char* mimetype, void* guac_kubernetes_send_current_argv(guac_user* user, void* data) { - guac_kubernetes_client* kubernetes_client = (guac_kubernetes_client*) data; + /* Defer to the batch handler, using the user socket */ + return guac_kubernetes_send_current_argv_batch(user->client, user->socket); + +} + +void* guac_kubernetes_send_current_argv_batch( + guac_client* client, guac_socket* socket) { + + guac_kubernetes_client* kubernetes_client = (guac_kubernetes_client*) client->data; guac_terminal* terminal = kubernetes_client->term; /* Send current color scheme */ - guac_user_stream_argv(user, user->socket, "text/plain", + guac_client_stream_argv(client, socket, "text/plain", GUAC_KUBERNETES_ARGV_COLOR_SCHEME, guac_terminal_get_color_scheme(terminal)); /* Send current font name */ - guac_user_stream_argv(user, user->socket, "text/plain", + guac_client_stream_argv(client, socket, "text/plain", GUAC_KUBERNETES_ARGV_FONT_NAME, guac_terminal_get_font_name(terminal)); /* Send current font size */ char font_size[64]; sprintf(font_size, "%i", guac_terminal_get_font_size(terminal)); - guac_user_stream_argv(user, user->socket, "text/plain", + guac_client_stream_argv(client, socket, "text/plain", GUAC_KUBERNETES_ARGV_FONT_SIZE, font_size); return NULL; diff --git a/src/protocols/kubernetes/argv.h b/src/protocols/kubernetes/argv.h index 307ebc71bd..dd73d42398 100644 --- a/src/protocols/kubernetes/argv.h +++ b/src/protocols/kubernetes/argv.h @@ -22,6 +22,7 @@ #define GUAC_KUBERNETES_ARGV_H #include "config.h" +#include "kubernetes.h" #include #include @@ -55,7 +56,7 @@ guac_argv_callback guac_kubernetes_argv_callback; * while the connection is running to the given user. Note that the user * receiving these values will not necessarily be able to set new values * themselves if their connection is read-only. This function can be used as - * the callback for guac_client_foreach_user() and guac_client_for_owner() + * the callback for guac_client_foreach_user() and guac_client_for_owner(). * * @param user * The user that should receive the values of all non-sensitive parameters @@ -70,5 +71,23 @@ guac_argv_callback guac_kubernetes_argv_callback; */ void* guac_kubernetes_send_current_argv(guac_user* user, void* data); +/** + * Sends the current values of all non-sensitive parameters which may be set + * while the connection is running to the all users associated with the + * provided socket. Note that the users receiving these values will not + * necessarily be able to set new values themselves if their connection is + * read-only. + * + * @param client + * The client associated with the users who should receive the values of + * all non-sensitive parameters which may be set while the connection is + * running. + * + * @return + * Always NULL. + */ +void* guac_kubernetes_send_current_argv_batch( + guac_client* client, guac_socket* socket); + #endif diff --git a/src/protocols/kubernetes/client.c b/src/protocols/kubernetes/client.c index ef0a502e58..b3a44e110a 100644 --- a/src/protocols/kubernetes/client.c +++ b/src/protocols/kubernetes/client.c @@ -78,44 +78,24 @@ static void guac_kubernetes_log(int level, const char* line) { } -/** - * Synchronize the connection state for the given pending user. - * - * @param user - * The pending user whose connection state should be synced. - * - * @param data - * Unused. - * - * @return - * Always NULL. - */ -static void* guac_kubernetes_sync_pending_user(guac_user* user, void* data) { - - guac_client* client = user->client; - guac_kubernetes_client* kubernetes_client = - (guac_kubernetes_client*) client->data; - - guac_terminal_dup(kubernetes_client->term, user, user->socket); - guac_kubernetes_send_current_argv(user, kubernetes_client); - guac_socket_flush(user->socket); - - return NULL; - -} - /** * A pending join handler implementation that will synchronize the connection * state for all pending users prior to them being promoted to full user. * * @param client - * The client whose pending users are about to be promoted. + * The client whose pending users are about to be promoted to full users, + * and therefore need their connection state synchronized. */ static void guac_kubernetes_join_pending_handler(guac_client* client) { - /* Synchronize each user one at a time */ - guac_client_foreach_pending_user( - client, guac_kubernetes_sync_pending_user, NULL); + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + /* Synchronize the terminal state to all pending users */ + guac_socket* broadcast_socket = client->pending_socket; + guac_terminal_sync_users(kubernetes_client->term, client, broadcast_socket); + guac_kubernetes_send_current_argv_batch(client, broadcast_socket); + guac_socket_flush(broadcast_socket); } diff --git a/src/protocols/rdp/channels/pipe-svc.c b/src/protocols/rdp/channels/pipe-svc.c index 2db42d6883..2611550892 100644 --- a/src/protocols/rdp/channels/pipe-svc.c +++ b/src/protocols/rdp/channels/pipe-svc.c @@ -41,9 +41,10 @@ void guac_rdp_pipe_svc_send_pipe(guac_socket* socket, guac_rdp_pipe_svc* pipe_sv } -void guac_rdp_pipe_svc_send_pipes(guac_user* user) { - guac_client* client = user->client; +void guac_rdp_pipe_svc_send_pipes( + guac_client* client, guac_socket* socket) { + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_common_list_lock(rdp_client->available_svc); @@ -51,12 +52,11 @@ void guac_rdp_pipe_svc_send_pipes(guac_user* user) { /* Send pipe for each allocated SVC's output stream */ guac_common_list_element* current = rdp_client->available_svc->head; while (current != NULL) { - guac_rdp_pipe_svc_send_pipe(user->socket, (guac_rdp_pipe_svc*) current->data); + guac_rdp_pipe_svc_send_pipe(socket, (guac_rdp_pipe_svc*) current->data); current = current->next; } guac_common_list_unlock(rdp_client->available_svc); - } void guac_rdp_pipe_svc_add(guac_client* client, guac_rdp_pipe_svc* pipe_svc) { diff --git a/src/protocols/rdp/channels/pipe-svc.h b/src/protocols/rdp/channels/pipe-svc.h index 242d4e50ac..f4575d4ec7 100644 --- a/src/protocols/rdp/channels/pipe-svc.h +++ b/src/protocols/rdp/channels/pipe-svc.h @@ -95,14 +95,18 @@ void guac_rdp_pipe_svc_send_pipe(guac_socket* socket, guac_rdp_pipe_svc* svc); /** * Sends the "pipe" instructions describing all static virtual channels - * available to the given user along that user's socket. Each pipe instruction - * will relate the associated SVC's underlying output stream with the SVC's - * name and the mimetype "application/octet-stream". + * available to the all users associated with the provided socket. Each pipe + * instruction will relate the associated SVC's underlying output stream with + * the SVC's name and the mimetype "application/octet-stream". * - * @param user - * The user to send the "pipe" instructions to. + * @param client + * The client associated with the users being sent the pipe instruction. + * + * @param socket + * The socket to send the pipe instruction accross. */ -void guac_rdp_pipe_svc_send_pipes(guac_user* user); +void guac_rdp_pipe_svc_send_pipes( + guac_client* client, guac_socket* socket); /** * Add the given SVC to the list of all available SVCs. This function must be diff --git a/src/protocols/rdp/client.c b/src/protocols/rdp/client.c index 5071a3a977..0228b7e395 100644 --- a/src/protocols/rdp/client.c +++ b/src/protocols/rdp/client.c @@ -80,32 +80,22 @@ static int is_writable_directory(const char* path) { } /** - * Synchronize the connection state for the given pending user. + * Add the provided user to the provided audio stream. * * @param user - * The pending user whose connection state should be synced. + * The pending user who should be added to the audio stream. * * @param data - * Unused. + * The audio stream that the user should be added to. * * @return * Always NULL. */ -static void* guac_rdp_sync_pending_user(guac_user* user, void* data) { +static void* guac_rdp_sync_pending_user_audio(guac_user* user, void* data) { - guac_rdp_client* rdp_client = (guac_rdp_client*) user->client->data; - - /* Synchronize any audio stream */ - if (rdp_client->audio) - guac_audio_stream_add_user(rdp_client->audio, user); - - /* Bring user up to date with any registered static channels */ - guac_rdp_pipe_svc_send_pipes(user); - - /* Synchronize with current display */ - guac_common_display_dup(rdp_client->display, user, user->socket); - - guac_socket_flush(user->socket); + /* Add the user to the stream */ + guac_audio_stream* audio = (guac_audio_stream*) data; + guac_audio_stream_add_user(audio, user); return NULL; @@ -120,9 +110,21 @@ static void* guac_rdp_sync_pending_user(guac_user* user, void* data) { */ static void guac_rdp_join_pending_handler(guac_client* client) { - /* Synchronize each user one at a time */ - guac_client_foreach_pending_user( - client, guac_rdp_sync_pending_user, NULL); + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + guac_socket* broadcast_socket = client->pending_socket; + + /* Synchronize any audio stream for each pending user */ + if (rdp_client->audio) + guac_client_foreach_pending_user( + client, guac_rdp_sync_pending_user_audio, rdp_client->audio); + + /* Bring user up to date with any registered static channels */ + guac_rdp_pipe_svc_send_pipes(client, broadcast_socket); + + /* Synchronize with current display */ + guac_common_display_dup(rdp_client->display, client, broadcast_socket); + + guac_socket_flush(broadcast_socket); } diff --git a/src/protocols/ssh/argv.c b/src/protocols/ssh/argv.c index a8f31f2ecb..6082b02ffa 100644 --- a/src/protocols/ssh/argv.c +++ b/src/protocols/ssh/argv.c @@ -70,26 +70,33 @@ int guac_ssh_argv_callback(guac_user* user, const char* mimetype, void* guac_ssh_send_current_argv(guac_user* user, void* data) { - guac_ssh_client* ssh_client = (guac_ssh_client*) data; + /* Defer to the batch handler, using the user's socket to send the data */ + guac_ssh_send_current_argv_batch(user->client, user->socket); + + return NULL; + +} + +void guac_ssh_send_current_argv_batch(guac_client* client, guac_socket* socket) { + + guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; guac_terminal* terminal = ssh_client->term; /* Send current color scheme */ - guac_user_stream_argv(user, user->socket, "text/plain", + guac_client_stream_argv(client, socket, "text/plain", GUAC_SSH_ARGV_COLOR_SCHEME, guac_terminal_get_color_scheme(terminal)); /* Send current font name */ - guac_user_stream_argv(user, user->socket, "text/plain", + guac_client_stream_argv(client, socket, "text/plain", GUAC_SSH_ARGV_FONT_NAME, guac_terminal_get_font_name(terminal)); /* Send current font size */ char font_size[64]; sprintf(font_size, "%i", guac_terminal_get_font_size(terminal)); - guac_user_stream_argv(user, user->socket, "text/plain", + guac_client_stream_argv(client, socket, "text/plain", GUAC_SSH_ARGV_FONT_SIZE, font_size); - return NULL; - } diff --git a/src/protocols/ssh/argv.h b/src/protocols/ssh/argv.h index 4cbdb4c847..0b32d09083 100644 --- a/src/protocols/ssh/argv.h +++ b/src/protocols/ssh/argv.h @@ -69,5 +69,23 @@ guac_argv_callback guac_ssh_argv_callback; */ void* guac_ssh_send_current_argv(guac_user* user, void* data); +/** + * Sends the current values of all non-sensitive parameters which may be set + * while the connection is running to the users associated with the provided + * socket. Note that the users receiving these values will not necessarily be + * able to set new values themselves if their connection is read-only. + * + * @param client + * The client associated with the users that should receive the values of + * all non-sensitive parameters which may be set while the connection is running. + * + * @param socket + * The socket to the arguments to the batch of users along. + * + * @return + * Always NULL. + */ +void guac_ssh_send_current_argv_batch(guac_client* client, guac_socket* socket); + #endif diff --git a/src/protocols/ssh/client.c b/src/protocols/ssh/client.c index 4b1c5393dd..c813808b8a 100644 --- a/src/protocols/ssh/client.c +++ b/src/protocols/ssh/client.c @@ -36,30 +36,6 @@ #include #include -/** - * Synchronize the connection state for the given pending user. - * - * @param user - * The pending user whose connection state should be synced. - * - * @param data - * Unused. - * - * @return - * Always NULL. - */ -static void* guac_ssh_sync_pending_user(guac_user* user, void* data) { - - guac_client* client = user->client; - guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; - - guac_terminal_dup(ssh_client->term, user, user->socket); - guac_ssh_send_current_argv(user, ssh_client); - guac_socket_flush(user->socket); - - return NULL; - -} /** * A pending join handler implementation that will synchronize the connection @@ -70,9 +46,13 @@ static void* guac_ssh_sync_pending_user(guac_user* user, void* data) { */ static void guac_ssh_join_pending_handler(guac_client* client) { - /* Synchronize each user one at a time */ - guac_client_foreach_pending_user( - client, guac_ssh_sync_pending_user, NULL); + guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; + + /* Synchronize the terminal state to all pending users */ + guac_socket* broadcast_socket = client->pending_socket; + guac_terminal_sync_users(ssh_client->term, client, broadcast_socket); + guac_ssh_send_current_argv_batch(client, broadcast_socket); + guac_socket_flush(broadcast_socket); } diff --git a/src/protocols/telnet/argv.c b/src/protocols/telnet/argv.c index 3ea3095efb..0424e323d8 100644 --- a/src/protocols/telnet/argv.c +++ b/src/protocols/telnet/argv.c @@ -65,26 +65,34 @@ int guac_telnet_argv_callback(guac_user* user, const char* mimetype, void* guac_telnet_send_current_argv(guac_user* user, void* data) { - guac_telnet_client* telnet_client = (guac_telnet_client*) data; + /* Defer to the batch handler, using the user's socket to send the data */ + guac_telnet_send_current_argv_batch(user->client, user->socket); + + return NULL; + +} + +void guac_telnet_send_current_argv_batch( + guac_client* client, guac_socket* socket) { + + guac_telnet_client* telnet_client = (guac_telnet_client*) client->data; guac_terminal* terminal = telnet_client->term; /* Send current color scheme */ - guac_user_stream_argv(user, user->socket, "text/plain", + guac_client_stream_argv(client, socket, "text/plain", GUAC_TELNET_ARGV_COLOR_SCHEME, guac_terminal_get_color_scheme(terminal)); /* Send current font name */ - guac_user_stream_argv(user, user->socket, "text/plain", + guac_client_stream_argv(client, socket, "text/plain", GUAC_TELNET_ARGV_FONT_NAME, guac_terminal_get_font_name(terminal)); /* Send current font size */ char font_size[64]; sprintf(font_size, "%i", guac_terminal_get_font_size(terminal)); - guac_user_stream_argv(user, user->socket, "text/plain", + guac_client_stream_argv(client, socket, "text/plain", GUAC_TELNET_ARGV_FONT_SIZE, font_size); - return NULL; - } diff --git a/src/protocols/telnet/argv.h b/src/protocols/telnet/argv.h index 60c1534f77..2c48c141b7 100644 --- a/src/protocols/telnet/argv.h +++ b/src/protocols/telnet/argv.h @@ -69,5 +69,24 @@ guac_argv_callback guac_telnet_argv_callback; */ void* guac_telnet_send_current_argv(guac_user* user, void* data); +/** + * Sends the current values of all non-sensitive parameters which may be set + * while the connection is running to the users associated with the provided + * socket. Note that the users receiving these values will not necessarily be + * able to set new values themselves if their connection is read-only. + * + * @param client + * The client associated with the users that should receive the values of + * all non-sensitive parameters which may be set while the connection is running. + * + * @param socket + * The socket to the arguments to the batch of users along. + * + * @return + * Always NULL. + */ +void guac_telnet_send_current_argv_batch( + guac_client* client, guac_socket* socket); + #endif diff --git a/src/protocols/telnet/client.c b/src/protocols/telnet/client.c index 71bf1b0a58..6f25463243 100644 --- a/src/protocols/telnet/client.c +++ b/src/protocols/telnet/client.c @@ -36,31 +36,6 @@ #include #include -/** - * Synchronize the connection state for the given pending user. - * - * @param user - * The pending user whose connection state should be synced. - * - * @param data - * Unused. - * - * @return - * Always NULL. - */ -static void* guac_telnet_sync_pending_user(guac_user* user, void* data) { - - guac_client* client = user->client; - guac_telnet_client* telnet_client = (guac_telnet_client*) client->data; - - guac_terminal_dup(telnet_client->term, user, user->socket); - guac_telnet_send_current_argv(user, telnet_client); - guac_socket_flush(user->socket); - - return NULL; - -} - /** * A pending join handler implementation that will synchronize the connection * state for all pending users prior to them being promoted to full user. @@ -70,9 +45,13 @@ static void* guac_telnet_sync_pending_user(guac_user* user, void* data) { */ static void guac_telnet_join_pending_handler(guac_client* client) { - /* Synchronize each user one at a time */ - guac_client_foreach_pending_user( - client, guac_telnet_sync_pending_user, NULL); + guac_telnet_client* telnet_client = (guac_telnet_client*) client->data; + + /* Synchronize the terminal state to all pending users */ + guac_socket* broadcast_socket = client->pending_socket; + guac_terminal_sync_users(telnet_client->term, client, broadcast_socket); + guac_telnet_send_current_argv_batch(client, broadcast_socket); + guac_socket_flush(broadcast_socket); } diff --git a/src/protocols/telnet/user.c b/src/protocols/telnet/user.c index 2c0e1c6131..e9d9df4631 100644 --- a/src/protocols/telnet/user.c +++ b/src/protocols/telnet/user.c @@ -70,13 +70,6 @@ int guac_telnet_user_join_handler(guac_user* user, int argc, char** argv) { } - /* If not owner, synchronize with current display */ - else { - guac_terminal_dup(telnet_client->term, user, user->socket); - guac_telnet_send_current_argv(user, telnet_client); - guac_socket_flush(user->socket); - } - /* Only handle events if not read-only */ if (!settings->read_only) { diff --git a/src/protocols/vnc/client.c b/src/protocols/vnc/client.c index 14dbe08037..9d7aa099d1 100644 --- a/src/protocols/vnc/client.c +++ b/src/protocols/vnc/client.c @@ -40,35 +40,29 @@ #include #include +#ifdef ENABLE_PULSE /** - * Synchronize the connection state for the given pending user. + * Add the provided user to the provided audio stream. * * @param user - * The pending user whose connection state should be synced. + * The pending user who should be added to the audio stream. * * @param data - * Unused. + * The audio stream that the user should be added to. * * @return * Always NULL. */ -static void* guac_vnc_sync_pending_user(guac_user* user, void* data) { - - guac_vnc_client* vnc_client = (guac_vnc_client*) user->client->data; - -#ifdef ENABLE_PULSE - /* Synchronize an audio stream */ - if (vnc_client->audio) - guac_pa_stream_add_user(vnc_client->audio, user); -#endif +static void* guac_vnc_sync_pending_user_audio(guac_user* user, void* data) { - /* Synchronize with current display */ - guac_common_display_dup(vnc_client->display, user, user->socket); - guac_socket_flush(user->socket); + /* Add the user to the stream */ + guac_pa_stream* audio = (guac_pa_stream*) data; + guac_pa_stream_add_user(audio, user); return NULL; } +#endif /** * A pending join handler implementation that will synchronize the connection @@ -79,9 +73,19 @@ static void* guac_vnc_sync_pending_user(guac_user* user, void* data) { */ static void guac_vnc_join_pending_handler(guac_client* client) { - /* Synchronize each user one at a time */ - guac_client_foreach_pending_user( - client, guac_vnc_sync_pending_user, NULL); + guac_vnc_client* vnc_client = (guac_vnc_client*) client->data; + guac_socket* broadcast_socket = client->pending_socket; + +#ifdef ENABLE_PULSE + /* Synchronize any audio stream for each pending user */ + if (vnc_client->audio) + guac_client_foreach_pending_user( + client, guac_vnc_sync_pending_user_audio, vnc_client->audio); +#endif + + /* Synchronize with current display */ + guac_common_display_dup(vnc_client->display, client, broadcast_socket); + guac_socket_flush(broadcast_socket); } diff --git a/src/protocols/vnc/user.c b/src/protocols/vnc/user.c index 4e6f473c85..74204f6241 100644 --- a/src/protocols/vnc/user.c +++ b/src/protocols/vnc/user.c @@ -74,21 +74,6 @@ int guac_vnc_user_join_handler(guac_user* user, int argc, char** argv) { } - /* If not owner, synchronize with current state */ - else { - -#ifdef ENABLE_PULSE - /* Synchronize an audio stream */ - if (vnc_client->audio) - guac_pa_stream_add_user(vnc_client->audio, user); -#endif - - /* Synchronize with current display */ - guac_common_display_dup(vnc_client->display, user, user->socket); - guac_socket_flush(user->socket); - - } - /* Only handle events if not read-only */ if (!settings->read_only) { diff --git a/src/terminal/display.c b/src/terminal/display.c index d4493252e7..133547be21 100644 --- a/src/terminal/display.c +++ b/src/terminal/display.c @@ -845,8 +845,16 @@ void guac_terminal_display_flush(guac_terminal_display* display) { void guac_terminal_display_dup(guac_terminal_display* display, guac_user* user, guac_socket* socket) { + /* Duplicate the display along the given socket, ignoring the user */ + guac_terminal_display_dup_batch(display, user->client, socket); + +} + +void guac_terminal_display_dup_batch( + guac_terminal_display* display, guac_client* client, guac_socket* socket) { + /* Create default surface */ - guac_common_surface_dup(display->display_surface, user, socket); + guac_common_surface_dup(display->display_surface, client, socket); /* Select layer is a child of the display layer */ guac_protocol_send_move(socket, display->select_layer, @@ -1051,4 +1059,4 @@ int guac_terminal_display_set_font(guac_terminal_display* display, return 0; -} \ No newline at end of file +} diff --git a/src/terminal/scrollbar.c b/src/terminal/scrollbar.c index a0eec8ec06..88a01a89a1 100644 --- a/src/terminal/scrollbar.c +++ b/src/terminal/scrollbar.c @@ -23,6 +23,7 @@ #include #include #include +#include #include @@ -331,8 +332,8 @@ static void calculate_state(guac_terminal_scrollbar* scrollbar, } -void guac_terminal_scrollbar_dup(guac_terminal_scrollbar* scrollbar, - guac_user* user, guac_socket* socket) { +void guac_terminal_scrollbar_dup_batch(guac_terminal_scrollbar* scrollbar, + guac_client* client, guac_socket* socket) { /* Get old state */ guac_terminal_scrollbar_render_state* state = &scrollbar->render_state; @@ -347,6 +348,14 @@ void guac_terminal_scrollbar_dup(guac_terminal_scrollbar* scrollbar, } +void guac_terminal_scrollbar_dup(guac_terminal_scrollbar* scrollbar, + guac_user* user, guac_socket* socket) { + + /* Duplicate the scrollbar across the provided socket, ignoring the user */ + guac_terminal_scrollbar_dup_batch(scrollbar, user->client, socket); + +} + void guac_terminal_scrollbar_flush(guac_terminal_scrollbar* scrollbar) { guac_socket* socket = scrollbar->client->socket; diff --git a/src/terminal/terminal.c b/src/terminal/terminal.c index 9d77a761e4..ab3d3865ac 100644 --- a/src/terminal/terminal.c +++ b/src/terminal/terminal.c @@ -2053,18 +2053,47 @@ int guac_terminal_create_typescript(guac_terminal* term, const char* path, } -void guac_terminal_dup(guac_terminal* term, guac_user* user, - guac_socket* socket) { +/** + * Synchronize the state of the provided terminal to a subset of users of + * the provided guac_client using the provided socket. + * + * @param client + * The client whose users should be synchronized. + * + * @param term + * The terminal state that should be synchronized to the users. + * + * @param socket + * The socket that should be used to communicate with the users. + */ +static void __guac_terminal_sync_socket( + guac_client* client, guac_terminal* term, guac_socket* socket) { /* Synchronize display state with new user */ guac_terminal_repaint_default_layer(term, socket); - guac_terminal_display_dup(term->display, user, socket); + guac_terminal_display_dup_batch(term->display, client, socket); /* Synchronize mouse cursor */ - guac_common_cursor_dup(term->cursor, user, socket); + guac_common_cursor_dup(term->cursor, client, socket); + + /* Paint scrollbar for joining users */ + guac_terminal_scrollbar_dup_batch(term->scrollbar, client, socket); + +} + +void guac_terminal_dup(guac_terminal* term, guac_user* user, + guac_socket* socket) { + + /* Ignore the user and just use the provided socket directly */ + __guac_terminal_sync_socket(user->client, term, socket); + +} + +void guac_terminal_sync_users( + guac_terminal* term, guac_client* client, guac_socket* socket) { - /* Paint scrollbar for joining user */ - guac_terminal_scrollbar_dup(term->scrollbar, user, socket); + /* Use the provided socket to synchronize state to the users */ + __guac_terminal_sync_socket(client, term, socket); } diff --git a/src/terminal/terminal/display.h b/src/terminal/terminal/display.h index 1d746211f8..1fa38f294f 100644 --- a/src/terminal/terminal/display.h +++ b/src/terminal/terminal/display.h @@ -340,6 +340,25 @@ void guac_terminal_display_flush(guac_terminal_display* display); void guac_terminal_display_dup(guac_terminal_display* display, guac_user* user, guac_socket* socket); +/** + * Initializes and syncs the current terminal display state for all joining + * users associated with the provided socket, sending the necessary instructions + * to completely recreate and redraw the terminal rendering over the given + * socket. + * + * @param display + * The terminal display to sync to the users associated with the provided + * socket. + * + * @param client + * The client whose users are joining. + * + * @param socket + * The socket over which any necessary instructions should be sent. + */ +void guac_terminal_display_dup_batch( + guac_terminal_display* display, guac_client* client, guac_socket* socket); + /** * Draws the text selection rectangle from the given coordinates to the given end coordinates. */ diff --git a/src/terminal/terminal/scrollbar.h b/src/terminal/terminal/scrollbar.h index c308337798..8dcc3a7364 100644 --- a/src/terminal/terminal/scrollbar.h +++ b/src/terminal/terminal/scrollbar.h @@ -260,6 +260,10 @@ void guac_terminal_scrollbar_flush(guac_terminal_scrollbar* scrollbar); * completely recreate and redraw the scrollbar rendering over the given * socket. * + * @deprecated guac_terminal_scrollbar_dup_batch() allows synchronizing + * multiple joining users at once using a broadcast socket, and should be + * used instead of this function + * * @param scrollbar * The scrollbar to sync to the given user. * @@ -272,6 +276,24 @@ void guac_terminal_scrollbar_flush(guac_terminal_scrollbar* scrollbar); void guac_terminal_scrollbar_dup(guac_terminal_scrollbar* scrollbar, guac_user* user, guac_socket* socket); +/** + * Forces a complete redraw / resync of scrollbar state for all joinging users + * associated with the provided socket, sending the necessary instructions to + * completely recreate and redraw the scrollbar rendering over the given + * socket. + * + * @param scrollbar + * The scrollbar to sync to the given users. + * + * @param client + * The client associated with the joining users. + * + * @param socket + * The socket over which any necessary instructions should be sent. + */ +void guac_terminal_scrollbar_dup_batch(guac_terminal_scrollbar* scrollbar, + guac_client* client, guac_socket* socket); + /** * Sets the minimum and maximum allowed scroll values of the given scrollbar * to the given values. If necessary, the current value of the scrollbar will diff --git a/src/terminal/terminal/terminal.h b/src/terminal/terminal/terminal.h index 082a3d23bc..221937f806 100644 --- a/src/terminal/terminal/terminal.h +++ b/src/terminal/terminal/terminal.h @@ -619,6 +619,9 @@ int guac_terminal_sendf(guac_terminal* term, const char* format, ...); * connection. All instructions necessary to replicate state are sent over the * given socket. * + * @deprecated The guac_terminal_sync_users method should be used when + * duplicating display state to a set of users. + * * @param term * The terminal emulator associated with the connection being joined. * @@ -632,6 +635,24 @@ int guac_terminal_sendf(guac_terminal* term, const char* format, ...); void guac_terminal_dup(guac_terminal* term, guac_user* user, guac_socket* socket); +/** + * Replicates the current display state to one or more users that are joining + * the connection. All instructions necessary to replicate state are sent over + * the given socket. The set of users receiving these instructions is + * determined solely by the socket chosen. + * + * @param term + * The terminal whose state should be synchronized to the users. + * + * @param client + * The client associated with the users to be synchronized. + * + * @param socket + * The socket to which the terminal state will be broadcast. + */ +void guac_terminal_sync_users( + guac_terminal* term, guac_client* client, guac_socket* socket); + /** * Resize the client display and terminal to the given pixel dimensions. *