From 6ea524ed6b227e62d036e3c6d4df5838833d3e19 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 8 May 2025 23:54:13 -0700 Subject: [PATCH 1/8] GUACAMOLE-2063: Explicitly sleep during wait in render thread to avoid contention on render state flag. --- src/libguac/display-render-thread.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/libguac/display-render-thread.c b/src/libguac/display-render-thread.c index 4cfcc602..e37a15af 100644 --- a/src/libguac/display-render-thread.c +++ b/src/libguac/display-render-thread.c @@ -84,7 +84,6 @@ static void* guac_display_render_loop(void* data) { /* Lacking explicit frame boundaries, handle the change in frame state, * continuing to accumulate frame modifications while still within * heuristically determined frame boundaries */ - int allowed_wait = 0; guac_timestamp frame_start = guac_timestamp_current(); do { @@ -117,21 +116,23 @@ static void* guac_display_render_loop(void* data) { * frame flush) */ cursor_state = render_thread->cursor_state; - /* Do not exceed a reasonable maximum framerate without an - * explicit frame boundary terminating the frame early */ - allowed_wait = GUAC_DISPLAY_RENDER_THREAD_MIN_FRAME_DURATION - frame_duration; - if (allowed_wait < 0) - allowed_wait = 0; - - /* Wait for further modifications or other changes to frame state */ - + /* Frame is no longer modified - prepare for possible future wait + * for further changes */ guac_flag_clear(&render_thread->state, GUAC_DISPLAY_RENDER_THREAD_STATE_FRAME_MODIFIED); guac_flag_unlock(&render_thread->state); + /* Do not exceed a reasonable maximum framerate without an + * explicit frame boundary terminating the frame early */ + int allowed_wait = GUAC_DISPLAY_RENDER_THREAD_MIN_FRAME_DURATION - frame_duration; + if (allowed_wait > 0) + guac_timestamp_msleep(allowed_wait); + + /* Wait for further modifications or other changes to frame state */ + } while (guac_flag_timedwait_and_lock(&render_thread->state, GUAC_DISPLAY_RENDER_THREAD_STATE_STOPPING | GUAC_DISPLAY_RENDER_THREAD_STATE_FRAME_READY - | GUAC_DISPLAY_RENDER_THREAD_STATE_FRAME_MODIFIED, allowed_wait)); + | GUAC_DISPLAY_RENDER_THREAD_STATE_FRAME_MODIFIED, 0)); /* Pass on cursor state for consumption by guac_display frame flush */ guac_rwlock_acquire_write_lock(&display->pending_frame.lock); From 50f5b5f93750e54db29039b1c6ae3992feaa7ed2 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 10 May 2025 14:16:12 -0700 Subject: [PATCH 2/8] GUACAMOLE-2063: Reset GDI invalid region after each draw (required for tracking within FreeRDP). If this is not done, the invalid region will gradually grow over time. --- src/protocols/rdp/gdi.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/protocols/rdp/gdi.c b/src/protocols/rdp/gdi.c index 89b785b7..f72040f9 100644 --- a/src/protocols/rdp/gdi.c +++ b/src/protocols/rdp/gdi.c @@ -138,6 +138,10 @@ BOOL guac_rdp_gdi_end_paint(rdpContext* context) { paint_complete: + /* Clear GDI state for future draws */ + gdi->primary->hdc->hwnd->invalid->null = TRUE; + gdi->primary->hdc->hwnd->ninvalid = 0; + /* There will be no further drawing operations */ rdp_client->current_context = NULL; guac_display_layer_close_raw(default_layer, current_context); From fe35de5459d2832fc3d25bf164f499b149f7dedf Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 13 May 2025 10:45:06 -0700 Subject: [PATCH 3/8] GUACAMOLE-2063: Decouple render thread updates from FreeRDP GDI updates. Doing otherwise tends to result in slower RDP updates being flushed as frames, amplifying the slowdown of those updates. --- src/protocols/rdp/gdi.c | 2 +- src/protocols/rdp/rdp.c | 7 +++++++ src/protocols/rdp/rdp.h | 6 ++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/protocols/rdp/gdi.c b/src/protocols/rdp/gdi.c index f72040f9..5a98cf68 100644 --- a/src/protocols/rdp/gdi.c +++ b/src/protocols/rdp/gdi.c @@ -134,7 +134,7 @@ BOOL guac_rdp_gdi_end_paint(rdpContext* context) { guac_rect_constrain(&dst_rect, ¤t_context->bounds); guac_rect_extend(¤t_context->dirty, &dst_rect); - guac_display_render_thread_notify_modified(rdp_client->render_thread); + rdp_client->gdi_modified = 1; paint_complete: diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index a6e7720d..577e7df9 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -617,6 +617,13 @@ static int guac_rdp_handle_connection(guac_client* client) { if (!guac_rdp_handle_events(rdp_client)) wait_result = -1; + /* Notify display of any changes to the GDI that may have occurred + * while handling events/messages */ + if (rdp_client->gdi_modified) { + guac_display_render_thread_notify_modified(rdp_client->render_thread); + rdp_client->gdi_modified = 0; + } + /* Test whether the RDP server is closing the connection */ int connection_closing; #ifdef HAVE_DISCONNECT_CONTEXT diff --git a/src/protocols/rdp/rdp.h b/src/protocols/rdp/rdp.h index 76ca18ca..05dd0d8c 100644 --- a/src/protocols/rdp/rdp.h +++ b/src/protocols/rdp/rdp.h @@ -114,6 +114,12 @@ typedef struct guac_rdp_client { */ guac_display_layer_raw_context* current_context; + /** + * Whether the graphical state of FreeRDP's GDI has changed since the last + * time a frame was sent to the client. + */ + int gdi_modified; + /** * The current instance of the guac_display render thread. If the thread * has not yet been started, this will be NULL. From cb4074ac15bb7f25a581d22f0d5522f273e1e550 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 14 May 2025 11:05:49 -0700 Subject: [PATCH 4/8] GUACAMOLE-2063: Move client processing lag compensation to render thread. Handling this within the worker threads appears to cause contention that can affect performance. --- src/libguac/display-render-thread.c | 33 +++++++++++++++++++++++--- src/libguac/display-worker.c | 36 ----------------------------- 2 files changed, 30 insertions(+), 39 deletions(-) diff --git a/src/libguac/display-render-thread.c b/src/libguac/display-render-thread.c index e37a15af..5eb8e333 100644 --- a/src/libguac/display-render-thread.c +++ b/src/libguac/display-render-thread.c @@ -62,6 +62,7 @@ static void* guac_display_render_loop(void* data) { guac_display_render_thread* render_thread = (guac_display_render_thread*) data; guac_display* display = render_thread->display; + guac_client* client = display->client; for (;;) { @@ -121,11 +122,37 @@ static void* guac_display_render_loop(void* data) { guac_flag_clear(&render_thread->state, GUAC_DISPLAY_RENDER_THREAD_STATE_FRAME_MODIFIED); guac_flag_unlock(&render_thread->state); + /* Use the amount of time that the client has been waiting + * for a frame vs. the amount of time that it took the + * client to process the most recently acknowledged frame + * to calculate the amount of additional delay required to + * allow the client to catch up. This value is used later, + * after everything else related to the frame has been + * finalized. */ + int time_since_last_frame = guac_timestamp_current() - client->last_sent_timestamp; + int processing_lag = guac_client_get_processing_lag(client); + int required_wait = processing_lag - time_since_last_frame; + /* Do not exceed a reasonable maximum framerate without an * explicit frame boundary terminating the frame early */ - int allowed_wait = GUAC_DISPLAY_RENDER_THREAD_MIN_FRAME_DURATION - frame_duration; - if (allowed_wait > 0) - guac_timestamp_msleep(allowed_wait); + int minimum_wait = GUAC_DISPLAY_RENDER_THREAD_MIN_FRAME_DURATION - frame_duration; + if (minimum_wait > required_wait) + required_wait = minimum_wait; + + /* Ensure we don't wait without bound when compensating for + * client-side processing delays */ + else if (required_wait > GUAC_DISPLAY_MAX_LAG_COMPENSATION) + required_wait = GUAC_DISPLAY_MAX_LAG_COMPENSATION; + + /* Wait for client to catch up, if necessary. Note that we don't do + * this via guac_flag_timedwait_and_lock() to avoid causing + * contention around the render_thread state lock. */ + if (required_wait > 0) { + guac_client_log(client, GUAC_LOG_TRACE, + "Waiting %ims to compensate for client-side " + "processing delays.\n", required_wait); + guac_timestamp_msleep(required_wait); + } /* Wait for further modifications or other changes to frame state */ diff --git a/src/libguac/display-worker.c b/src/libguac/display-worker.c index 6c785d88..813306fd 100644 --- a/src/libguac/display-worker.c +++ b/src/libguac/display-worker.c @@ -401,17 +401,6 @@ void* guac_display_worker_thread(void* data) { cursor->last_frame.height); } - /* Use the amount of time that the client has been waiting - * for a frame vs. the amount of time that it took the - * client to process the most recently acknowledged frame - * to calculate the amount of additional delay required to - * allow the client to catch up. This value is used later, - * after everything else related to the frame has been - * finalized. */ - int time_since_last_frame = guac_timestamp_current() - client->last_sent_timestamp; - int processing_lag = guac_client_get_processing_lag(client); - int required_wait = processing_lag - time_since_last_frame; - /* Allow connected clients to move forward with rendering */ guac_client_end_multiple_frames(client, display->last_frame.frames); @@ -457,31 +446,6 @@ void* guac_display_worker_thread(void* data) { guac_flag_clear(&display->render_state, GUAC_DISPLAY_RENDER_STATE_FRAME_IN_PROGRESS); guac_flag_unlock(&display->render_state); - /* Exclude local, server-side frame processing latency from - * waiting period */ - int latency = (int) (guac_timestamp_current() - display->last_frame.timestamp); - if (latency >= 0) { - guac_client_log(display->client, GUAC_LOG_TRACE, - "Rendering latency: %ims (%i:1 frame)\n", - latency, display->last_frame.frames); - required_wait -= latency; - } - - /* Ensure we don't wait without bound when compensating for - * client-side processing delays */ - if (required_wait > GUAC_DISPLAY_MAX_LAG_COMPENSATION) - required_wait = GUAC_DISPLAY_MAX_LAG_COMPENSATION; - - /* Allow connected clients to catch up if they're taking - * longer to process frames than the server is taking to - * generate them */ - if (required_wait > 0) { - guac_client_log(display->client, GUAC_LOG_TRACE, - "Waiting %ims to compensate for client-side " - "processing delays.\n", required_wait); - guac_timestamp_msleep(required_wait); - } - has_outstanding_frames = display->frame_deferred; } From 509641bf56e8fd0c7d6bed7262a33a4fad889137 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 15 May 2025 10:41:03 -0700 Subject: [PATCH 5/8] GUACAMOLE-2063: Consider processing lag even when frame boundaries are explicit. --- src/libguac/display-render-thread.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/libguac/display-render-thread.c b/src/libguac/display-render-thread.c index 5eb8e333..13f70369 100644 --- a/src/libguac/display-render-thread.c +++ b/src/libguac/display-render-thread.c @@ -97,20 +97,6 @@ static void* guac_display_render_loop(void* data) { break; } - /* Use explicit frame boundaries whenever available */ - if (render_thread->state.value & GUAC_DISPLAY_RENDER_THREAD_STATE_FRAME_READY) { - - rendered_frames = render_thread->frames; - render_thread->frames = 0; - - guac_flag_clear(&render_thread->state, - GUAC_DISPLAY_RENDER_THREAD_STATE_FRAME_READY - | GUAC_DISPLAY_RENDER_THREAD_STATE_FRAME_MODIFIED); - guac_flag_unlock(&render_thread->state); - break; - - } - /* Copy cursor state for later flushing with final frame, * regardless of whether it's changed (there's really no need to * compare here - that will be done by the actual guac_display @@ -154,6 +140,20 @@ static void* guac_display_render_loop(void* data) { guac_timestamp_msleep(required_wait); } + /* Use explicit frame boundaries whenever available */ + if (render_thread->state.value & GUAC_DISPLAY_RENDER_THREAD_STATE_FRAME_READY) { + + rendered_frames = render_thread->frames; + render_thread->frames = 0; + + guac_flag_clear(&render_thread->state, + GUAC_DISPLAY_RENDER_THREAD_STATE_FRAME_READY + | GUAC_DISPLAY_RENDER_THREAD_STATE_FRAME_MODIFIED); + guac_flag_unlock(&render_thread->state); + break; + + } + /* Wait for further modifications or other changes to frame state */ } while (guac_flag_timedwait_and_lock(&render_thread->state, From 336196966153f3525afc5f9b1c73093a39f1810a Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 16 May 2025 23:40:39 -0700 Subject: [PATCH 6/8] GUACAMOLE-2063: Ensure all received data from remote desktop server is handled each attempted frame. --- src/protocols/rdp/rdp.c | 29 +++++++++++++++++------------ src/protocols/vnc/vnc.c | 6 ++++-- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index 577e7df9..c2555157 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -612,10 +612,23 @@ static int guac_rdp_handle_connection(guac_client* client) { if (wait_result < 0) break; - /* Handle any queued FreeRDP events (this may result in RDP messages - * being sent), aborting later if FreeRDP event handling fails */ - if (!guac_rdp_handle_events(rdp_client)) - wait_result = -1; + int connection_closing; + do { + + /* Handle any queued FreeRDP events (this may result in RDP messages + * being sent), aborting later if FreeRDP event handling fails */ + if (!guac_rdp_handle_events(rdp_client)) + wait_result = -1; + + /* Test whether the RDP server is closing the connection */ +#ifdef HAVE_DISCONNECT_CONTEXT + connection_closing = freerdp_shall_disconnect_context(rdp_inst->context); +#else + connection_closing = freerdp_shall_disconnect(rdp_inst); +#endif + + } while (!connection_closing && + (wait_result = rdp_guac_client_wait_for_events(client, 0)) > 0); /* Notify display of any changes to the GDI that may have occurred * while handling events/messages */ @@ -624,14 +637,6 @@ static int guac_rdp_handle_connection(guac_client* client) { rdp_client->gdi_modified = 0; } - /* Test whether the RDP server is closing the connection */ - int connection_closing; -#ifdef HAVE_DISCONNECT_CONTEXT - connection_closing = freerdp_shall_disconnect_context(rdp_inst->context); -#else - connection_closing = freerdp_shall_disconnect(rdp_inst); -#endif - /* Close connection cleanly if server is disconnecting */ if (connection_closing) guac_rdp_client_abort(client, rdp_inst); diff --git a/src/protocols/vnc/vnc.c b/src/protocols/vnc/vnc.c index 6ec4f493..8f5fa912 100644 --- a/src/protocols/vnc/vnc.c +++ b/src/protocols/vnc/vnc.c @@ -629,7 +629,7 @@ void* guac_vnc_client_thread(void* data) { /* Wait for data and construct a reasonable frame */ int wait_result = guac_vnc_wait_for_messages(rfb_client, GUAC_VNC_MESSAGE_CHECK_INTERVAL); - if (wait_result > 0) { + while (wait_result > 0) { /* Handle any message received */ if (!guac_vnc_handle_messages(client)) { @@ -639,10 +639,12 @@ void* guac_vnc_client_thread(void* data) { break; } + wait_result = guac_vnc_wait_for_messages(rfb_client, 0); + } /* If an error occurs, log it and fail */ - else if (wait_result < 0) + if (wait_result < 0) guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Connection closed."); } From 95466a1ae9845f15a9180544640886121f8b7237 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 16 May 2025 23:40:02 -0700 Subject: [PATCH 7/8] GUACAMOLE-2063: Use event queue for RDP input events. --- src/protocols/rdp/Makefile.am | 1 + src/protocols/rdp/client.c | 11 ++ src/protocols/rdp/input-queue.c | 282 ++++++++++++++++++++++++++++++++ src/protocols/rdp/input.c | 162 ++++-------------- src/protocols/rdp/input.h | 177 ++++++++++++++++++++ src/protocols/rdp/rdp.c | 19 ++- src/protocols/rdp/rdp.h | 57 +++++++ src/protocols/rdp/settings.c | 3 - 8 files changed, 577 insertions(+), 135 deletions(-) create mode 100644 src/protocols/rdp/input-queue.c diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index 12a2e68b..dc9638e3 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -67,6 +67,7 @@ libguac_client_rdp_la_SOURCES = \ fs.c \ gdi.c \ input.c \ + input-queue.c \ keyboard.c \ keymap.c \ log.c \ diff --git a/src/protocols/rdp/client.c b/src/protocols/rdp/client.c index 263ab8d2..8b2e683d 100644 --- a/src/protocols/rdp/client.c +++ b/src/protocols/rdp/client.c @@ -205,6 +205,13 @@ int guac_client_init(guac_client* client, int argc, char** argv) { guac_rdp_client* rdp_client = guac_mem_zalloc(sizeof(guac_rdp_client)); client->data = rdp_client; + /* Create queue for input events (to avoid RDP I/O blocking processing of + * further Guacamole instructions) and associated signalling handle */ + guac_fifo_init(&rdp_client->input_events, &rdp_client->input_events_items, + GUAC_RDP_INPUT_EVENT_QUEUE_SIZE, sizeof(guac_rdp_input_event)); + + rdp_client->input_event_queued = CreateEvent(NULL, TRUE, FALSE, NULL); + /* Init clipboard */ rdp_client->clipboard = guac_rdp_clipboard_alloc(client); @@ -255,6 +262,10 @@ int guac_rdp_client_free_handler(guac_client* client) { /* Wait for client thread */ pthread_join(rdp_client->client_thread, NULL); + /* Clean up event queue and associated signalling handle */ + guac_fifo_destroy(&rdp_client->input_events); + CloseHandle(rdp_client->input_event_queued); + /* Free parsed settings */ if (rdp_client->settings != NULL) guac_rdp_settings_free(rdp_client->settings); diff --git a/src/protocols/rdp/input-queue.c b/src/protocols/rdp/input-queue.c new file mode 100644 index 00000000..1606d479 --- /dev/null +++ b/src/protocols/rdp/input-queue.c @@ -0,0 +1,282 @@ +/* + * 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 "channels/disp.h" +#include "channels/rdpei.h" +#include "input.h" +#include "guacamole/display.h" +#include "keyboard.h" +#include "rdp.h" +#include "settings.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/** + * Processes a single mouse event, updating client state and sending any + * associated RDP PDUs via the provided RDP client instance. + * + * @param rdp_client + * The RDP client instance that should be updated and used to send any PDUs + * associated with the event. + * + * @param event + * The mouse event to process. + */ +static void guac_rdp_handle_mouse_event(guac_rdp_client* rdp_client, + const guac_rdp_input_event* event) { + + /* This function exclusively processes mouse events, and it's on the caller + * to ensure only mouse events are provided */ + GUAC_ASSERT(event->type == GUAC_RDP_INPUT_EVENT_MOUSE); + + guac_user* user = event->user; + int x = event->details.mouse.x; + int y = event->details.mouse.y; + int mask = event->details.mouse.mask; + + guac_rwlock_acquire_read_lock(&(rdp_client->lock)); + + /* Skip if not yet connected */ + freerdp* rdp_inst = rdp_client->rdp_inst; + if (rdp_inst == NULL) + goto complete; + + /* Store current mouse location/state */ + guac_display_render_thread_notify_user_moved_mouse(rdp_client->render_thread, user, x, y, mask); + + /* Report mouse position within recording */ + if (rdp_client->recording != NULL) + guac_recording_report_mouse(rdp_client->recording, x, y, mask); + + /* If button mask unchanged, just send move event */ + if (mask == rdp_client->mouse_button_mask) { + pthread_mutex_lock(&(rdp_client->message_lock)); + GUAC_RDP_CONTEXT(rdp_inst)->input->MouseEvent( + GUAC_RDP_CONTEXT(rdp_inst)->input, PTR_FLAGS_MOVE, x, y); + pthread_mutex_unlock(&(rdp_client->message_lock)); + } + + /* Otherwise, send events describing button change */ + else { + + /* Mouse buttons which have JUST become released */ + int released_mask = rdp_client->mouse_button_mask & ~mask; + + /* Mouse buttons which have JUST become pressed */ + int pressed_mask = ~rdp_client->mouse_button_mask & mask; + + /* Release event */ + if (released_mask & 0x07) { + + /* Calculate flags */ + int flags = 0; + if (released_mask & 0x01) flags |= PTR_FLAGS_BUTTON1; + if (released_mask & 0x02) flags |= PTR_FLAGS_BUTTON3; + if (released_mask & 0x04) flags |= PTR_FLAGS_BUTTON2; + + pthread_mutex_lock(&(rdp_client->message_lock)); + GUAC_RDP_CONTEXT(rdp_inst)->input->MouseEvent( + GUAC_RDP_CONTEXT(rdp_inst)->input, flags, x, y); + pthread_mutex_unlock(&(rdp_client->message_lock)); + + } + + /* Press event */ + if (pressed_mask & 0x07) { + + /* Calculate flags */ + int flags = PTR_FLAGS_DOWN; + if (pressed_mask & 0x01) flags |= PTR_FLAGS_BUTTON1; + if (pressed_mask & 0x02) flags |= PTR_FLAGS_BUTTON3; + if (pressed_mask & 0x04) flags |= PTR_FLAGS_BUTTON2; + if (pressed_mask & 0x08) flags |= PTR_FLAGS_WHEEL | 0x78; + if (pressed_mask & 0x10) flags |= PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | 0x88; + + /* Send event */ + pthread_mutex_lock(&(rdp_client->message_lock)); + GUAC_RDP_CONTEXT(rdp_inst)->input->MouseEvent( + GUAC_RDP_CONTEXT(rdp_inst)->input, flags, x, y); + pthread_mutex_unlock(&(rdp_client->message_lock)); + + } + + /* Scroll event */ + if (pressed_mask & 0x18) { + + /* Down */ + if (pressed_mask & 0x08) { + pthread_mutex_lock(&(rdp_client->message_lock)); + GUAC_RDP_CONTEXT(rdp_inst)->input->MouseEvent( + GUAC_RDP_CONTEXT(rdp_inst)->input, PTR_FLAGS_WHEEL | 0x78, x, y); + pthread_mutex_unlock(&(rdp_client->message_lock)); + } + + /* Up */ + if (pressed_mask & 0x10) { + pthread_mutex_lock(&(rdp_client->message_lock)); + GUAC_RDP_CONTEXT(rdp_inst)->input->MouseEvent( + GUAC_RDP_CONTEXT(rdp_inst)->input, PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | 0x88, x, y); + pthread_mutex_unlock(&(rdp_client->message_lock)); + } + + } + + rdp_client->mouse_button_mask = mask; + } + +complete: + guac_rwlock_release_lock(&(rdp_client->lock)); + +} + +/** + * Processes a single key event, updating client state and sending any + * associated RDP PDUs via the provided RDP client instance. + * + * @param rdp_client + * The RDP client instance that should be updated and used to send any PDUs + * associated with the event. + * + * @param event + * The key event to process. + */ +static void guac_rdp_handle_key_event(guac_rdp_client* rdp_client, + const guac_rdp_input_event* event) { + + /* This function exclusively processes key events, and it's on the caller + * to ensure only key events are provided */ + GUAC_ASSERT(event->type == GUAC_RDP_INPUT_EVENT_KEY); + + int keysym = event->details.key.keysym; + int pressed = event->details.key.pressed; + + guac_rwlock_acquire_read_lock(&(rdp_client->lock)); + + /* Report key state within recording */ + if (rdp_client->recording != NULL) + guac_recording_report_key(rdp_client->recording, + keysym, pressed); + + /* Skip if keyboard not yet ready */ + if (rdp_client->keyboard == NULL) + goto complete; + + /* Update keysym state */ + guac_rdp_keyboard_update_keysym(rdp_client->keyboard, + keysym, pressed, GUAC_RDP_KEY_SOURCE_CLIENT); + +complete: + guac_rwlock_release_lock(&(rdp_client->lock)); + +} + +/** + * Processes a single touch event, updating client state and sending any + * associated RDP PDUs via the provided RDP client instance. + * + * @param rdp_client + * The RDP client instance that should be updated and used to send any PDUs + * associated with the event. + * + * @param event + * The touch event to process. + */ +static void guac_rdp_handle_touch_event(guac_rdp_client* rdp_client, + const guac_rdp_input_event* event) { + + /* This function exclusively processes touch. events, and it's on the + * caller to ensure only touch. events are provided */ + GUAC_ASSERT(event->type == GUAC_RDP_INPUT_EVENT_TOUCH); + + int id = event->details.touch.id; + int x = event->details.touch.x; + int y = event->details.touch.y; + int x_radius = event->details.touch.x_radius; + int y_radius = event->details.touch.y_radius; + double angle = event->details.touch.angle; + double force = event->details.touch.force; + + guac_rwlock_acquire_read_lock(&(rdp_client->lock)); + + /* Skip if not yet connected */ + freerdp* rdp_inst = rdp_client->rdp_inst; + if (rdp_inst == NULL) + goto complete; + + /* Report touch event within recording */ + if (rdp_client->recording != NULL) + guac_recording_report_touch(rdp_client->recording, id, x, y, + x_radius, y_radius, angle, force); + + /* Forward touch event along RDPEI channel */ + guac_rdp_rdpei_touch_update(rdp_client->rdpei, id, x, y, force); + +complete: + guac_rwlock_release_lock(&(rdp_client->lock)); + +} + +void guac_rdp_input_event_enqueue(guac_rdp_client* rdp_client, + const guac_rdp_input_event* input_event) { + + guac_fifo_enqueue_and_lock(&rdp_client->input_events, input_event); + SetEvent(rdp_client->input_event_queued); + guac_fifo_unlock(&rdp_client->input_events); + +} + +void guac_rdp_handle_input_events(guac_rdp_client* rdp_client) { + + guac_fifo_lock(&rdp_client->input_events); + + guac_rdp_input_event input_event; + while (guac_fifo_timed_dequeue(&rdp_client->input_events, &input_event, 0)) { + switch (input_event.type) { + + /* Mouse event */ + case GUAC_RDP_INPUT_EVENT_MOUSE: + guac_rdp_handle_mouse_event(rdp_client, &input_event); + break; + + /* Keyboard event */ + case GUAC_RDP_INPUT_EVENT_KEY: + guac_rdp_handle_key_event(rdp_client, &input_event); + break; + + /* Touch event */ + case GUAC_RDP_INPUT_EVENT_TOUCH: + guac_rdp_handle_touch_event(rdp_client, &input_event); + break; + + } + } + + ResetEvent(rdp_client->input_event_queued); + guac_fifo_unlock(&rdp_client->input_events); + +} diff --git a/src/protocols/rdp/input.c b/src/protocols/rdp/input.c index d74a9c79..34f9e7c4 100644 --- a/src/protocols/rdp/input.c +++ b/src/protocols/rdp/input.c @@ -40,100 +40,19 @@ int guac_rdp_user_mouse_handler(guac_user* user, int x, int y, int mask) { guac_client* client = user->client; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - guac_rwlock_acquire_read_lock(&(rdp_client->lock)); - - /* Skip if not yet connected */ - freerdp* rdp_inst = rdp_client->rdp_inst; - if (rdp_inst == NULL) - goto complete; - - /* Store current mouse location/state */ - guac_display_render_thread_notify_user_moved_mouse(rdp_client->render_thread, user, x, y, mask); - - /* Report mouse position within recording */ - if (rdp_client->recording != NULL) - guac_recording_report_mouse(rdp_client->recording, x, y, mask); - - /* If button mask unchanged, just send move event */ - if (mask == rdp_client->mouse_button_mask) { - pthread_mutex_lock(&(rdp_client->message_lock)); - GUAC_RDP_CONTEXT(rdp_inst)->input->MouseEvent( - GUAC_RDP_CONTEXT(rdp_inst)->input, PTR_FLAGS_MOVE, x, y); - pthread_mutex_unlock(&(rdp_client->message_lock)); - } - - /* Otherwise, send events describing button change */ - else { - - /* Mouse buttons which have JUST become released */ - int released_mask = rdp_client->mouse_button_mask & ~mask; - - /* Mouse buttons which have JUST become pressed */ - int pressed_mask = ~rdp_client->mouse_button_mask & mask; - - /* Release event */ - if (released_mask & 0x07) { - - /* Calculate flags */ - int flags = 0; - if (released_mask & 0x01) flags |= PTR_FLAGS_BUTTON1; - if (released_mask & 0x02) flags |= PTR_FLAGS_BUTTON3; - if (released_mask & 0x04) flags |= PTR_FLAGS_BUTTON2; - - pthread_mutex_lock(&(rdp_client->message_lock)); - GUAC_RDP_CONTEXT(rdp_inst)->input->MouseEvent( - GUAC_RDP_CONTEXT(rdp_inst)->input, flags, x, y); - pthread_mutex_unlock(&(rdp_client->message_lock)); - + guac_rdp_input_event mouse_event = { + .type = GUAC_RDP_INPUT_EVENT_MOUSE, + .user = user, + .details.mouse = { + .x = x, + .y = y, + .mask = mask } + }; - /* Press event */ - if (pressed_mask & 0x07) { - - /* Calculate flags */ - int flags = PTR_FLAGS_DOWN; - if (pressed_mask & 0x01) flags |= PTR_FLAGS_BUTTON1; - if (pressed_mask & 0x02) flags |= PTR_FLAGS_BUTTON3; - if (pressed_mask & 0x04) flags |= PTR_FLAGS_BUTTON2; - if (pressed_mask & 0x08) flags |= PTR_FLAGS_WHEEL | 0x78; - if (pressed_mask & 0x10) flags |= PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | 0x88; - - /* Send event */ - pthread_mutex_lock(&(rdp_client->message_lock)); - GUAC_RDP_CONTEXT(rdp_inst)->input->MouseEvent( - GUAC_RDP_CONTEXT(rdp_inst)->input, flags, x, y); - pthread_mutex_unlock(&(rdp_client->message_lock)); - - } - - /* Scroll event */ - if (pressed_mask & 0x18) { - - /* Down */ - if (pressed_mask & 0x08) { - pthread_mutex_lock(&(rdp_client->message_lock)); - GUAC_RDP_CONTEXT(rdp_inst)->input->MouseEvent( - GUAC_RDP_CONTEXT(rdp_inst)->input, PTR_FLAGS_WHEEL | 0x78, x, y); - pthread_mutex_unlock(&(rdp_client->message_lock)); - } - - /* Up */ - if (pressed_mask & 0x10) { - pthread_mutex_lock(&(rdp_client->message_lock)); - GUAC_RDP_CONTEXT(rdp_inst)->input->MouseEvent( - GUAC_RDP_CONTEXT(rdp_inst)->input, PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | 0x88, x, y); - pthread_mutex_unlock(&(rdp_client->message_lock)); - } - - } - - rdp_client->mouse_button_mask = mask; - } - -complete: - guac_rwlock_release_lock(&(rdp_client->lock)); - + guac_rdp_input_event_enqueue(rdp_client, &mouse_event); return 0; + } int guac_rdp_user_touch_handler(guac_user* user, int id, int x, int y, @@ -142,52 +61,41 @@ int guac_rdp_user_touch_handler(guac_user* user, int id, int x, int y, guac_client* client = user->client; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - guac_rwlock_acquire_read_lock(&(rdp_client->lock)); - - /* Skip if not yet connected */ - freerdp* rdp_inst = rdp_client->rdp_inst; - if (rdp_inst == NULL) - goto complete; - - /* Report touch event within recording */ - if (rdp_client->recording != NULL) - guac_recording_report_touch(rdp_client->recording, id, x, y, - x_radius, y_radius, angle, force); - - /* Forward touch event along RDPEI channel */ - guac_rdp_rdpei_touch_update(rdp_client->rdpei, id, x, y, force); - -complete: - guac_rwlock_release_lock(&(rdp_client->lock)); + guac_rdp_input_event touch_event = { + .type = GUAC_RDP_INPUT_EVENT_TOUCH, + .user = user, + .details.touch = { + .id = id, + .x = x, + .y = y, + .x_radius = x_radius, + .y_radius = y_radius, + .angle = angle, + .force = force + } + }; + guac_rdp_input_event_enqueue(rdp_client, &touch_event); return 0; + } int guac_rdp_user_key_handler(guac_user* user, int keysym, int pressed) { guac_client* client = user->client; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - int retval = 0; - guac_rwlock_acquire_read_lock(&(rdp_client->lock)); + guac_rdp_input_event key_event = { + .type = GUAC_RDP_INPUT_EVENT_KEY, + .user = user, + .details.key = { + .keysym = keysym, + .pressed = pressed + } + }; - /* Report key state within recording */ - if (rdp_client->recording != NULL) - guac_recording_report_key(rdp_client->recording, - keysym, pressed); - - /* Skip if keyboard not yet ready */ - if (rdp_client->keyboard == NULL) - goto complete; - - /* Update keysym state */ - retval = guac_rdp_keyboard_update_keysym(rdp_client->keyboard, - keysym, pressed, GUAC_RDP_KEY_SOURCE_CLIENT); - -complete: - guac_rwlock_release_lock(&(rdp_client->lock)); - - return retval; + guac_rdp_input_event_enqueue(rdp_client, &key_event); + return 0; } diff --git a/src/protocols/rdp/input.h b/src/protocols/rdp/input.h index eb9e4825..b2e7fdf9 100644 --- a/src/protocols/rdp/input.h +++ b/src/protocols/rdp/input.h @@ -22,6 +22,183 @@ #include +/** + * All event types supported by the guac_rdp_input_event structure. + */ +typedef enum guac_rdp_input_event_type { + + /** + * A mouse event, such as mouse movement or press/release of a mouse + * button. + */ + GUAC_RDP_INPUT_EVENT_MOUSE, + + /** + * A key event, such as press/release of a keyboard key. + */ + GUAC_RDP_INPUT_EVENT_KEY, + + /** + * A touch event, such as movement of an established touch or a change in + * touch pressure. + */ + GUAC_RDP_INPUT_EVENT_TOUCH + +} guac_rdp_input_event_type; + +/** + * Event details specific to GUAC_RDP_INPUT_EVENT_MOUSE events. + */ +typedef struct guac_rdp_input_event_mouse_details { + + /** + * The X coordinate of the mouse pointer, in pixels. This value is not + * guaranteed to be within the bounds of the display area. + */ + int x; + + /** + * The Y coordinate of the mouse pointer, in pixels. This value is not + * guaranteed to be within the bounds of the display area. + */ + 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 mask; + +} guac_rdp_input_event_mouse_details; + +/** + * Event details specific to GUAC_RDP_INPUT_EVENT_KEY events. + */ +typedef struct guac_rdp_input_event_key_details { + + /** + * The X11 keysym of the key that was pressed or released. + */ + int keysym; + + /** + * Non-zero if the key was pressed, zero if the key was released. + */ + int pressed; + +} guac_rdp_input_event_key_details; + +/** + * Event details specific to GUAC_RDP_INPUT_EVENT_TOUCH events. + */ +typedef struct guac_rdp_input_event_touch_details { + + /** + * An arbitrary integer ID which uniquely identifies this contact relative + * to other active contacts. + */ + int id; + + /** + * The X coordinate of the center of the touch contact within the display + * when the event occurred, in pixels. This value is not guaranteed to be + * within the bounds of the display area. + */ + int x; + + /** + * The Y coordinate of the center of the touch contact within the display + * when the event occurred, in pixels. This value is not guaranteed to be + * within the bounds of the display area. + */ + int y; + + /** + * The X radius of the ellipse covering the general area of the touch + * contact, in pixels. + */ + int x_radius; + + /** + * The Y radius of the ellipse covering the general area of the touch + * contact, in pixels. + */ + int y_radius; + + /** + * The rough angle of clockwise rotation of the general area of the touch + * contact, in degrees. + */ + double angle; + + /** + * The relative force exerted by the touch contact, where 0 is no force + * (the touch has been lifted) and 1 is maximum force (the maximum amount + * of force representable by the device). + */ + double force; + +} guac_rdp_input_event_touch_details; + +/** + * Generic input event that may represent any one of several possible event + * types, as dictated by guac_rdp_input_event_type. The available details of + * the event depend on the event type. + */ +typedef struct guac_rdp_input_event { + + /** + * The type of this event. This value dictates which event details are + * relevant. + */ + guac_rdp_input_event_type type; + + /** + * The user that originated this event. NOTE: This pointer is not + * guaranteed to be valid and MUST NOT be dereferenced without verifying + * the pointer is actually still valid. + */ + guac_user* user; + + /** + * Event details that are type-specific. + */ + union { + + /** + * Event details specific to GUAC_RDP_INPUT_EVENT_MOUSE events. This + * details structure MUST NOT be used for any other event type. Doing + * otherwise may overwrite valid event details. + */ + guac_rdp_input_event_mouse_details mouse; + + /** + * Event details specific to GUAC_RDP_INPUT_EVENT_KEY events. This + * details structure MUST NOT be used for any other event type. Doing + * otherwise may overwrite valid event details. + */ + guac_rdp_input_event_key_details key; + + /** + * Event details specific to GUAC_RDP_INPUT_EVENT_TOUCH events. This + * details structure MUST NOT be used for any other event type. Doing + * otherwise may overwrite valid event details. + */ + guac_rdp_input_event_touch_details touch; + + } details; + +} guac_rdp_input_event; + /** * Handler for Guacamole user mouse events. */ diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index c2555157..5f4249e0 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -35,7 +35,7 @@ #include "error.h" #include "fs.h" #include "gdi.h" -#include "guacamole/display-types.h" +#include "input.h" #include "keyboard.h" #include "plugins/channels.h" #include "pointer.h" @@ -433,20 +433,26 @@ static DWORD rdp_freerdp_verify_certificate(freerdp* instance, * A positive value if messages are ready, zero if the specified timeout * period elapsed, or a negative value if an error occurs. */ -static int rdp_guac_client_wait_for_messages(guac_client* client, +static int rdp_guac_client_wait_for_events(guac_client* client, int timeout_msecs) { guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; freerdp* rdp_inst = rdp_client->rdp_inst; HANDLE handles[GUAC_RDP_MAX_FILE_DESCRIPTORS]; - int num_handles = freerdp_get_event_handles(GUAC_RDP_CONTEXT(rdp_inst), handles, - GUAC_RDP_MAX_FILE_DESCRIPTORS); + int num_handles = 0; + + handles[num_handles++] = rdp_client->input_event_queued; + + num_handles += freerdp_get_event_handles(GUAC_RDP_CONTEXT(rdp_inst), + handles + num_handles, GUAC_RDP_MAX_FILE_DESCRIPTORS - num_handles); /* Wait for data and construct a reasonable frame */ DWORD result = WaitForMultipleObjects(num_handles, handles, FALSE, timeout_msecs); + ResetEvent(rdp_client->input_event_queued); + /* Translate WaitForMultipleObjects() return values */ switch (result) { @@ -608,7 +614,7 @@ static int guac_rdp_handle_connection(guac_client* client) { /* Wait for data and construct a reasonable frame */ - int wait_result = rdp_guac_client_wait_for_messages(client, GUAC_RDP_MESSAGE_CHECK_INTERVAL); + int wait_result = rdp_guac_client_wait_for_events(client, GUAC_RDP_MESSAGE_CHECK_INTERVAL); if (wait_result < 0) break; @@ -637,6 +643,9 @@ static int guac_rdp_handle_connection(guac_client* client) { rdp_client->gdi_modified = 0; } + /* Handle any input events that have been received */ + guac_rdp_handle_input_events(rdp_client); + /* Close connection cleanly if server is disconnecting */ if (connection_closing) guac_rdp_client_abort(client, rdp_inst); diff --git a/src/protocols/rdp/rdp.h b/src/protocols/rdp/rdp.h index 05dd0d8c..dc7c264d 100644 --- a/src/protocols/rdp/rdp.h +++ b/src/protocols/rdp/rdp.h @@ -28,6 +28,7 @@ #include "common/list.h" #include "config.h" #include "fs.h" +#include "input.h" #include "keyboard.h" #include "print-job.h" #include "settings.h" @@ -44,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -65,6 +67,11 @@ #define GUAC_RDP_CONTEXT(rdp_instance) ((rdp_instance)) #endif +/** + * The maximum number of input events to allow in the event queue. + */ +#define GUAC_RDP_INPUT_EVENT_QUEUE_SIZE 4096 + /** * RDP-specific client data. */ @@ -126,6 +133,28 @@ typedef struct guac_rdp_client { */ guac_display_render_thread* render_thread; + /** + * Queue of mouse, keyboard, and touch events. These events are accumulated + * and flushed within the RDP client thread to avoid spending excessive + * time within Guacamole's event handlers. If an attempt to send an RDP + * event to the RDP server takes a noticable amount of time, that time will + * otherwise block handling of Guacamole events, including critical events + * like "sync" (resulting in miscalculation of processing lag). + */ + guac_fifo input_events; + + /** + * Storage for the input_events queue (see above). + */ + guac_rdp_input_event input_events_items[GUAC_RDP_INPUT_EVENT_QUEUE_SIZE]; + + /** + * FreeRDP event handle that is set with SetEvent() when at least one input + * event has been added to the input_events queue. When all input events + * have been processed, this event handle is cleared with ResetEvent(). + */ + HANDLE input_event_queued; + /** * The current state of the keyboard with respect to the RDP session. */ @@ -259,4 +288,32 @@ typedef struct rdp_freerdp_context { */ void* guac_rdp_client_thread(void* data); +/** + * Enqueues the given input event for future processing by a call to + * guac_rdp_handle_input_events(). The values from the input event will be + * copied and stored independently of the provided pointer. Calling this + * function will automatically notify any threads waiting on the + * input_event_queued handle. + * + * @param rdp_client + * The RDP client instance associated with the RDP session receiving the + * event. + * + * @param input_event + * The input event to add to the queue. + */ +void guac_rdp_input_event_enqueue(guac_rdp_client* rdp_client, + const guac_rdp_input_event* input_event); + +/** + * Processes all events that have been enqueued with + * guac_rdp_input_event_enqueue(), clearing the event queue and the state of + * the input_event_queued handle. Events are processed in the order they are + * received. + * + * @param rdp_client + * The RDP client instance whose queued input events should be processed. + */ +void guac_rdp_handle_input_events(guac_rdp_client* rdp_client); + #endif diff --git a/src/protocols/rdp/settings.c b/src/protocols/rdp/settings.c index 8c5afdfc..b0d36259 100644 --- a/src/protocols/rdp/settings.c +++ b/src/protocols/rdp/settings.c @@ -1780,9 +1780,6 @@ void guac_rdp_push_settings(guac_client* client, rdp_settings->FrameMarkerCommandEnabled = TRUE; rdp_settings->SurfaceFrameMarkerEnabled = TRUE; - /* Always handle input events asynchronously (rather than synchronously - * with the rest of FreeRDP's event loop, including graphics) */ - rdp_settings->AsyncInput = TRUE; /* Enable RemoteFX / Graphics Pipeline */ if (guac_settings->enable_gfx) { From 3f5d0b49cd077f055e7b95d7b6b36690ee361260 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 16 May 2025 23:41:39 -0700 Subject: [PATCH 8/8] GUACAMOLE-2063: Enable support for RDP orders that are enabled by default in FreeRDP. --- src/protocols/rdp/settings.c | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/src/protocols/rdp/settings.c b/src/protocols/rdp/settings.c index b0d36259..4c10c15d 100644 --- a/src/protocols/rdp/settings.c +++ b/src/protocols/rdp/settings.c @@ -1535,6 +1535,9 @@ void guac_rdp_push_settings(guac_client* client, freerdp_settings_set_bool(rdp_settings, FreeRDP_FrameMarkerCommandEnabled, TRUE); freerdp_settings_set_bool(rdp_settings, FreeRDP_SurfaceFrameMarkerEnabled, TRUE); + freerdp_settings_set_bool(rdp_settings, FreeRDP_FastPathInput, TRUE); + freerdp_settings_set_bool(rdp_settings, FreeRDP_FastPathOutput, TRUE); + /* Enable RemoteFX / Graphics Pipeline */ if (guac_settings->enable_gfx) { @@ -1548,7 +1551,6 @@ void guac_rdp_push_settings(guac_client* client, } /* Required for RemoteFX / Graphics Pipeline */ - freerdp_settings_set_bool(rdp_settings, FreeRDP_FastPathOutput, TRUE); freerdp_settings_set_uint32(rdp_settings, FreeRDP_ColorDepth, RDP_GFX_REQUIRED_DEPTH); freerdp_settings_set_bool(rdp_settings, FreeRDP_SoftwareGdi, TRUE); @@ -1733,19 +1735,6 @@ void guac_rdp_push_settings(guac_client* client, freerdp_settings_set_uint32(rdp_settings, FreeRDP_OsMinorType, OSMINORTYPE_UNSPECIFIED); freerdp_settings_set_bool(rdp_settings, FreeRDP_DesktopResize, TRUE); - /* Claim support only for specific updates, independent of FreeRDP defaults */ - BYTE* order_support = freerdp_settings_get_pointer_writable(rdp_settings, FreeRDP_OrderSupport); - if (order_support) { - ZeroMemory(order_support, GUAC_RDP_ORDER_SUPPORT_LENGTH); - order_support[NEG_DSTBLT_INDEX] = TRUE; - order_support[NEG_SCRBLT_INDEX] = TRUE; - order_support[NEG_MEMBLT_INDEX] = !guac_settings->disable_bitmap_caching; - order_support[NEG_MEMBLT_V2_INDEX] = !guac_settings->disable_bitmap_caching; - order_support[NEG_GLYPH_INDEX_INDEX] = !guac_settings->disable_glyph_caching; - order_support[NEG_FAST_INDEX_INDEX] = !guac_settings->disable_glyph_caching; - order_support[NEG_FAST_GLYPH_INDEX] = !guac_settings->disable_glyph_caching; - } - #ifdef HAVE_RDPSETTINGS_ALLOWUNANOUNCEDORDERSFROMSERVER /* Do not consider server use of unannounced orders to be a fatal error */ freerdp_settings_set_bool(rdp_settings, FreeRDP_AllowUnanouncedOrdersFromServer, TRUE); @@ -1780,6 +1769,8 @@ void guac_rdp_push_settings(guac_client* client, rdp_settings->FrameMarkerCommandEnabled = TRUE; rdp_settings->SurfaceFrameMarkerEnabled = TRUE; + rdp_settings->FastPathInput = TRUE; + rdp_settings->FastPathOutput = TRUE; /* Enable RemoteFX / Graphics Pipeline */ if (guac_settings->enable_gfx) { @@ -1794,7 +1785,6 @@ void guac_rdp_push_settings(guac_client* client, } /* Required for RemoteFX / Graphics Pipeline */ - rdp_settings->FastPathOutput = TRUE; rdp_settings->ColorDepth = RDP_GFX_REQUIRED_DEPTH; rdp_settings->SoftwareGdi = TRUE; @@ -1979,16 +1969,6 @@ void guac_rdp_push_settings(guac_client* client, rdp_settings->OsMinorType = OSMINORTYPE_UNSPECIFIED; rdp_settings->DesktopResize = TRUE; - /* Claim support only for specific updates, independent of FreeRDP defaults */ - ZeroMemory(rdp_settings->OrderSupport, GUAC_RDP_ORDER_SUPPORT_LENGTH); - rdp_settings->OrderSupport[NEG_DSTBLT_INDEX] = TRUE; - rdp_settings->OrderSupport[NEG_SCRBLT_INDEX] = TRUE; - rdp_settings->OrderSupport[NEG_MEMBLT_INDEX] = !guac_settings->disable_bitmap_caching; - rdp_settings->OrderSupport[NEG_MEMBLT_V2_INDEX] = !guac_settings->disable_bitmap_caching; - rdp_settings->OrderSupport[NEG_GLYPH_INDEX_INDEX] = !guac_settings->disable_glyph_caching; - rdp_settings->OrderSupport[NEG_FAST_INDEX_INDEX] = !guac_settings->disable_glyph_caching; - rdp_settings->OrderSupport[NEG_FAST_GLYPH_INDEX] = !guac_settings->disable_glyph_caching; - #ifdef HAVE_RDPSETTINGS_ALLOWUNANOUNCEDORDERSFROMSERVER /* Do not consider server use of unannounced orders to be a fatal error */ rdp_settings->AllowUnanouncedOrdersFromServer = TRUE;