Merge changes from patch branch back to main.

This commit is contained in:
Corentin SORIANO 2025-05-28 08:25:09 +02:00
commit fc5073fb69
No known key found for this signature in database
12 changed files with 662 additions and 224 deletions

View File

@ -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 (;;) {
@ -84,7 +85,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 {
@ -97,6 +97,49 @@ static void* guac_display_render_loop(void* data) {
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
* frame flush) */
cursor_state = render_thread->cursor_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);
/* 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 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);
}
/* Use explicit frame boundaries whenever available */
if (render_thread->state.value & GUAC_DISPLAY_RENDER_THREAD_STATE_FRAME_READY) {
@ -111,27 +154,12 @@ static void* guac_display_render_loop(void* data) {
}
/* 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
* 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 */
guac_flag_clear(&render_thread->state, GUAC_DISPLAY_RENDER_THREAD_STATE_FRAME_MODIFIED);
guac_flag_unlock(&render_thread->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);

View File

@ -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;
}

View File

@ -67,6 +67,7 @@ libguac_client_rdp_la_SOURCES = \
fs.c \
gdi.c \
input.c \
input-queue.c \
keyboard.c \
keymap.c \
log.c \

View File

@ -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 display update module */
rdp_client->disp = guac_rdp_disp_alloc(client);
@ -252,6 +259,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);

View File

@ -134,10 +134,14 @@ BOOL guac_rdp_gdi_end_paint(rdpContext* context) {
guac_rect_constrain(&dst_rect, &current_context->bounds);
guac_rect_extend(&current_context->dirty, &dst_rect);
guac_display_render_thread_notify_modified(rdp_client->render_thread);
rdp_client->gdi_modified = 1;
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);

View File

@ -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 <freerdp/freerdp.h>
#include <freerdp/input.h>
#include <guacamole/assert.h>
#include <guacamole/client.h>
#include <guacamole/display.h>
#include <guacamole/recording.h>
#include <guacamole/rwlock.h>
#include <guacamole/user.h>
#include <stdlib.h>
/**
* 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);
}

View File

@ -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;
}

View File

@ -22,6 +22,183 @@
#include <guacamole/user.h>
/**
* 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.
*/

View File

@ -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,23 +614,38 @@ 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;
/* 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 */
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);
connection_closing = freerdp_shall_disconnect_context(rdp_inst->context);
#else
connection_closing = freerdp_shall_disconnect(rdp_inst);
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 */
if (rdp_client->gdi_modified) {
guac_display_render_thread_notify_modified(rdp_client->render_thread);
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);

View File

@ -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 <guacamole/audio.h>
#include <guacamole/client.h>
#include <guacamole/display.h>
#include <guacamole/fifo.h>
#include <guacamole/rwlock.h>
#include <guacamole/recording.h>
#include <winpr/wtypes.h>
@ -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.
*/
@ -114,12 +121,40 @@ 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.
*/
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.
*/
@ -253,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

View File

@ -1562,6 +1562,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) {
@ -1575,7 +1578,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);
@ -1760,19 +1762,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);
@ -1807,9 +1796,8 @@ 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;
rdp_settings->FastPathInput = TRUE;
rdp_settings->FastPathOutput = TRUE;
/* Enable RemoteFX / Graphics Pipeline */
if (guac_settings->enable_gfx) {
@ -1824,7 +1812,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;
@ -2009,16 +1996,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;

View File

@ -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.");
}