GUACAMOLE-249: Migrate RDPSND support to FreeRDP 2.0.0 plugin API.

This commit is contained in:
Michael Jumper 2019-12-21 17:44:43 -08:00
parent 0497a33ece
commit 6f2b124472
5 changed files with 333 additions and 145 deletions

View File

@ -130,9 +130,9 @@ libguac_client_rdp_la_LIBADD = \
freerdp_LTLIBRARIES = \
libguacai-client.la \
libguacsnd-client.la \
libguacsvc-client.la
# libguacdr-client.la
# libguacsnd-client.la
freerdpdir = ${libdir}/freerdp2
@ -200,25 +200,25 @@ libguacai_client_la_LIBADD = \
# RDPSND
#
#libguacsnd_client_la_SOURCES = \
# guac_rdpsnd/rdpsnd_messages.c \
# guac_rdpsnd/rdpsnd_service.c
#
#libguacsnd_client_la_CFLAGS = \
# -Werror -Wall -Iinclude \
# @COMMON_INCLUDE@ \
# @COMMON_SSH_INCLUDE@ \
# @LIBGUAC_INCLUDE@ \
# @RDP_CFLAGS@
#
#libguacsnd_client_la_LDFLAGS = \
# -module -avoid-version -shared \
# @PTHREAD_LIBS@ \
# @RDP_LIBS@
#
#libguacsnd_client_la_LIBADD = \
# @COMMON_LTLIB@ \
# @LIBGUAC_LTLIB@
libguacsnd_client_la_SOURCES = \
guac_rdpsnd/rdpsnd_messages.c \
guac_rdpsnd/rdpsnd_service.c
libguacsnd_client_la_CFLAGS = \
-Werror -Wall -Iinclude \
@COMMON_INCLUDE@ \
@COMMON_SSH_INCLUDE@ \
@LIBGUAC_INCLUDE@ \
@RDP_CFLAGS@
libguacsnd_client_la_LDFLAGS = \
-module -avoid-version -shared \
@PTHREAD_LIBS@ \
@RDP_LIBS@
libguacsnd_client_la_LIBADD = \
@COMMON_LTLIB@ \
@LIBGUAC_LTLIB@
#
# Static Virtual Channels

View File

@ -26,7 +26,6 @@
#include <pthread.h>
#include <stdlib.h>
#include <freerdp/utils/svc_plugin.h>
#include <guacamole/audio.h>
#include <guacamole/client.h>
#include <winpr/stream.h>
@ -34,7 +33,7 @@
/* MESSAGE HANDLERS */
void guac_rdpsnd_formats_handler(guac_rdpsndPlugin* rdpsnd,
void guac_rdpsnd_formats_handler(guac_rdpsnd* rdpsnd,
wStream* input_stream, guac_rdpsnd_pdu_header* header) {
int server_format_count;
@ -182,8 +181,9 @@ void guac_rdpsnd_formats_handler(guac_rdpsndPlugin* rdpsnd,
Stream_SetPointer(output_stream, output_stream_end);
/* Send accepted formats */
pthread_mutex_lock(&(rdp_client->rdp_lock));
svc_plugin_send((rdpSvcPlugin*)rdpsnd, output_stream);
rdpsnd->entry_points.pVirtualChannelWriteEx(rdpsnd->init_handle,
rdpsnd->open_handle, Stream_Buffer(output_stream),
Stream_GetPosition(output_stream), output_stream);
/* If version greater than 6, must send Quality Mode PDU */
if (server_version >= 6) {
@ -196,24 +196,21 @@ void guac_rdpsnd_formats_handler(guac_rdpsndPlugin* rdpsnd,
Stream_Write_UINT16(output_stream, HIGH_QUALITY);
Stream_Write_UINT16(output_stream, 0);
svc_plugin_send((rdpSvcPlugin*)rdpsnd, output_stream);
}
rdpsnd->entry_points.pVirtualChannelWriteEx(rdpsnd->init_handle,
rdpsnd->open_handle, Stream_Buffer(output_stream),
Stream_GetPosition(output_stream), output_stream);
pthread_mutex_unlock(&(rdp_client->rdp_lock));
}
}
/* server is getting a feel of the round trip time */
void guac_rdpsnd_training_handler(guac_rdpsndPlugin* rdpsnd,
void guac_rdpsnd_training_handler(guac_rdpsnd* rdpsnd,
wStream* input_stream, guac_rdpsnd_pdu_header* header) {
int data_size;
wStream* output_stream;
/* Get associated client data */
guac_client* client = rdpsnd->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Read timestamp and data size */
Stream_Read_UINT16(input_stream, rdpsnd->server_timestamp);
Stream_Read_UINT16(input_stream, data_size);
@ -226,13 +223,13 @@ void guac_rdpsnd_training_handler(guac_rdpsndPlugin* rdpsnd,
Stream_Write_UINT16(output_stream, rdpsnd->server_timestamp);
Stream_Write_UINT16(output_stream, data_size);
pthread_mutex_lock(&(rdp_client->rdp_lock));
svc_plugin_send((rdpSvcPlugin*) rdpsnd, output_stream);
pthread_mutex_unlock(&(rdp_client->rdp_lock));
rdpsnd->entry_points.pVirtualChannelWriteEx(rdpsnd->init_handle,
rdpsnd->open_handle, Stream_Buffer(output_stream),
Stream_GetPosition(output_stream), output_stream);
}
void guac_rdpsnd_wave_info_handler(guac_rdpsndPlugin* rdpsnd,
void guac_rdpsnd_wave_info_handler(guac_rdpsnd* rdpsnd,
wStream* input_stream, guac_rdpsnd_pdu_header* header) {
int format;
@ -270,11 +267,9 @@ void guac_rdpsnd_wave_info_handler(guac_rdpsndPlugin* rdpsnd,
}
void guac_rdpsnd_wave_handler(guac_rdpsndPlugin* rdpsnd,
void guac_rdpsnd_wave_handler(guac_rdpsnd* rdpsnd,
wStream* input_stream, guac_rdpsnd_pdu_header* header) {
rdpSvcPlugin* plugin = (rdpSvcPlugin*)rdpsnd;
/* Get associated client data */
guac_client* client = rdpsnd->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
@ -307,16 +302,16 @@ void guac_rdpsnd_wave_handler(guac_rdpsndPlugin* rdpsnd,
Stream_Write_UINT8(output_stream, 0);
/* Send Wave Confirmation PDU */
pthread_mutex_lock(&(rdp_client->rdp_lock));
svc_plugin_send(plugin, output_stream);
pthread_mutex_unlock(&(rdp_client->rdp_lock));
rdpsnd->entry_points.pVirtualChannelWriteEx(rdpsnd->init_handle,
rdpsnd->open_handle, Stream_Buffer(output_stream),
Stream_GetPosition(output_stream), output_stream);
/* We no longer expect to receive wave data */
rdpsnd->next_pdu_is_wave = FALSE;
}
void guac_rdpsnd_close_handler(guac_rdpsndPlugin* rdpsnd,
void guac_rdpsnd_close_handler(guac_rdpsnd* rdpsnd,
wStream* input_stream, guac_rdpsnd_pdu_header* header) {
/* Do nothing */

View File

@ -18,8 +18,8 @@
*/
#ifndef __GUAC_RDPSND_MESSAGES_H
#define __GUAC_RDPSND_MESSAGES_H
#ifndef GUAC_RDPSND_MESSAGES_H
#define GUAC_RDPSND_MESSAGES_H
#include "config.h"
@ -128,7 +128,7 @@ typedef struct guac_rdpsnd_pdu_header {
* The header content of the SNDC_FORMATS PDU. All RDPSND messages contain
* the same header information.
*/
void guac_rdpsnd_formats_handler(guac_rdpsndPlugin* rdpsnd,
void guac_rdpsnd_formats_handler(guac_rdpsnd* rdpsnd,
wStream* input_stream, guac_rdpsnd_pdu_header* header);
/**
@ -149,7 +149,7 @@ void guac_rdpsnd_formats_handler(guac_rdpsndPlugin* rdpsnd,
* The header content of the SNDC_TRAINING PDU. All RDPSND messages contain
* the same header information.
*/
void guac_rdpsnd_training_handler(guac_rdpsndPlugin* rdpsnd,
void guac_rdpsnd_training_handler(guac_rdpsnd* rdpsnd,
wStream* input_stream, guac_rdpsnd_pdu_header* header);
/**
@ -172,7 +172,7 @@ void guac_rdpsnd_training_handler(guac_rdpsndPlugin* rdpsnd,
* The header content of the SNDC_WAVE PDU. All RDPSND messages contain
* the same header information.
*/
void guac_rdpsnd_wave_info_handler(guac_rdpsndPlugin* rdpsnd,
void guac_rdpsnd_wave_info_handler(guac_rdpsnd* rdpsnd,
wStream* input_stream, guac_rdpsnd_pdu_header* header);
/**
@ -191,7 +191,7 @@ void guac_rdpsnd_wave_info_handler(guac_rdpsndPlugin* rdpsnd,
* The header content of the SNDWAV PDU. All RDPSND messages contain
* the same header information.
*/
void guac_rdpsnd_wave_handler(guac_rdpsndPlugin* rdpsnd,
void guac_rdpsnd_wave_handler(guac_rdpsnd* rdpsnd,
wStream* input_stream, guac_rdpsnd_pdu_header* header);
/**
@ -211,7 +211,7 @@ void guac_rdpsnd_wave_handler(guac_rdpsndPlugin* rdpsnd,
* The header content of the SNDC_CLOSE PDU. All RDPSND messages contain
* the same header information.
*/
void guac_rdpsnd_close_handler(guac_rdpsndPlugin* rdpsnd,
void guac_rdpsnd_close_handler(guac_rdpsnd* rdpsnd,
wStream* input_stream, guac_rdpsnd_pdu_header* header);
#endif

View File

@ -26,74 +26,23 @@
#include <string.h>
#include <freerdp/constants.h>
#include <freerdp/utils/svc_plugin.h>
#include <guacamole/client.h>
#include <winpr/stream.h>
/**
* Entry point for RDPSND virtual channel.
* Processes data received along the RDPSND channel via a
* CHANNEL_EVENT_DATA_RECEIVED event, forwarding the data along an established,
* outbound pipe stream to the Guacamole client.
*
* @param rdpsnd
* The guac_rdpsnd structure representing the RDPSND channel.
*
* @param input_stream
* The data that was received.
*/
int VirtualChannelEntry(PCHANNEL_ENTRY_POINTS pEntryPoints) {
/* Allocate plugin */
guac_rdpsndPlugin* rdpsnd =
(guac_rdpsndPlugin*) calloc(1, sizeof(guac_rdpsndPlugin));
/* Init channel def */
strcpy(rdpsnd->plugin.channel_def.name, "rdpsnd");
rdpsnd->plugin.channel_def.options =
CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP;
/* Set callbacks */
rdpsnd->plugin.connect_callback = guac_rdpsnd_process_connect;
rdpsnd->plugin.receive_callback = guac_rdpsnd_process_receive;
rdpsnd->plugin.event_callback = guac_rdpsnd_process_event;
rdpsnd->plugin.terminate_callback = guac_rdpsnd_process_terminate;
/* Finish init */
svc_plugin_init((rdpSvcPlugin*) rdpsnd, pEntryPoints);
return 1;
}
/*
* Service Handlers
*/
void guac_rdpsnd_process_connect(rdpSvcPlugin* plugin) {
guac_rdpsndPlugin* rdpsnd = (guac_rdpsndPlugin*) plugin;
/* Get client from plugin parameters */
guac_client* client = rdpsnd->client =
(guac_client*) plugin->channel_entry_points.pExtendedData;
/* NULL out pExtendedData so we don't lose our guac_client due to an
* automatic free() within libfreerdp */
plugin->channel_entry_points.pExtendedData = NULL;
#ifdef RDPSVCPLUGIN_INTERVAL_MS
/* Update every 10 ms */
plugin->interval_ms = 10;
#endif
/* Log that sound has been loaded */
guac_client_log(client, GUAC_LOG_INFO, "guacsnd connected.");
}
void guac_rdpsnd_process_terminate(rdpSvcPlugin* plugin) {
free(plugin);
}
void guac_rdpsnd_process_event(rdpSvcPlugin* plugin, wMessage* event) {
freerdp_event_free(event);
}
void guac_rdpsnd_process_receive(rdpSvcPlugin* plugin,
static void guac_rdpsnd_process_receive(guac_rdpsnd* rdpsnd,
wStream* input_stream) {
guac_rdpsndPlugin* rdpsnd = (guac_rdpsndPlugin*) plugin;
guac_rdpsnd_pdu_header header;
/* Read RDPSND PDU header */
@ -137,3 +86,243 @@ void guac_rdpsnd_process_receive(rdpSvcPlugin* plugin,
}
/**
* Event handler for events which deal with data transmitted over the RDPSND
* channel. This specific implementation of the event handler currently
* handles only the CHANNEL_EVENT_DATA_RECEIVED event, delegating actual
* handling of that event to guac_rdpsnd_process_receive().
*
* The FreeRDP requirements for this function follow those of the
* VirtualChannelOpenEventEx callback defined within Microsoft's RDP API:
*
* https://docs.microsoft.com/en-us/previous-versions/windows/embedded/aa514754%28v%3dmsdn.10%29
*
* @param user_param
* The pointer to arbitrary data originally passed via the first parameter
* of the pVirtualChannelInitEx() function call when the associated channel
* was initialized. The pVirtualChannelInitEx() function is exposed within
* the channel entry points structure.
*
* @param open_handle
* The handle which identifies the channel itself, typically referred to
* within the FreeRDP source as OpenHandle.
*
* @param event
* An integer representing the event that should be handled. This will be
* either CHANNEL_EVENT_DATA_RECEIVED, CHANNEL_EVENT_WRITE_CANCELLED, or
* CHANNEL_EVENT_WRITE_COMPLETE.
*
* @param data
* The data received, for CHANNEL_EVENT_DATA_RECEIVED events, and the value
* passed as user data to pVirtualChannelWriteEx() for
* CHANNEL_EVENT_WRITE_* events (note that user data for
* pVirtualChannelWriteEx() as implemented by FreeRDP MUST either be NULL
* or a wStream containing the data written).
*
* @param data_length
* The number of bytes of event-specific data.
*
* @param total_length
* The total number of bytes written to the RDP server in a single write
* operation.
*
* NOTE: The meaning of total_length is unclear. The above description was
* written mainly through referencing the documentation in MSDN. Real-world
* use will need to be consulted, likely within the FreeRDP source, before
* this value can be reliably used. The current implementation of this
* handler ignores this parameter.
*
* @param data_flags
* The result of a bitwise OR of the CHANNEL_FLAG_* flags which apply to
* the data received. This value is relevant only to
* CHANNEL_EVENT_DATA_RECEIVED events. Valid flags are CHANNEL_FLAG_FIRST,
* CHANNEL_FLAG_LAST, and CHANNEL_FLAG_ONLY. The flag CHANNEL_FLAG_MIDDLE
* is not itself a flag, but the absence of both CHANNEL_FLAG_FIRST and
* CHANNEL_FLAG_LAST.
*/
static VOID guac_rdpsnd_handle_open_event(LPVOID user_param,
DWORD open_handle, UINT event, LPVOID data, UINT32 data_length,
UINT32 total_length, UINT32 data_flags) {
/* Ignore all events except for received data */
if (event != CHANNEL_EVENT_DATA_RECEIVED)
return;
guac_rdpsnd* rdpsnd = (guac_rdpsnd*) user_param;
/* Validate relevant handle matches that of the RDPSND channel */
if (open_handle != rdpsnd->open_handle) {
guac_client_log(rdpsnd->client, GUAC_LOG_WARNING, "%i bytes of data "
"received from within the remote desktop session for the "
"RDPSND channel are being dropped because the relevant open "
"handle (0x%X) does not match the open handle of RDPSND "
"(0x%X).", data_length, rdpsnd->channel_def.name, open_handle,
rdpsnd->open_handle);
return;
}
wStream* input_stream = Stream_New(data, data_length);
guac_rdpsnd_process_receive(rdpsnd, input_stream);
Stream_Free(input_stream, FALSE);
}
/**
* Processes a CHANNEL_EVENT_CONNECTED event, completing the
* connection/initialization process of the RDPSND channel.
*
* @param rdpsnd
* The guac_rdpsnd structure representing the RDPSND channel.
*/
static void guac_rdpsnd_process_connect(guac_rdpsnd* rdpsnd) {
/* Open FreeRDP side of connected channel */
UINT32 open_status =
rdpsnd->entry_points.pVirtualChannelOpenEx(rdpsnd->init_handle,
&rdpsnd->open_handle, rdpsnd->channel_def.name,
guac_rdpsnd_handle_open_event);
/* Warn if the channel cannot be opened after all */
if (open_status != CHANNEL_RC_OK) {
guac_client_log(rdpsnd->client, GUAC_LOG_WARNING, "RDPSND channel "
"could not be opened: %s (error %i)",
WTSErrorToString(open_status), open_status);
return;
}
/* Log that sound has been loaded */
guac_client_log(rdpsnd->client, GUAC_LOG_INFO, "RDPSND channel "
"connected.");
}
/**
* Processes a CHANNEL_EVENT_TERMINATED event, freeing all resources associated
* with the RDPSND channel.
*
* @param rdpsnd
* The guac_rdpsnd structure representing the RDPSND channel.
*/
static void guac_rdpsnd_process_terminate(guac_rdpsnd* rdpsnd) {
guac_client_log(rdpsnd->client, GUAC_LOG_INFO, "RDPSND channel disconnected.");
free(rdpsnd);
}
/**
* Event handler for events which deal with the overall lifecycle of the RDPSND
* channel. This specific implementation of the event handler currently
* handles only CHANNEL_EVENT_CONNECTED and CHANNEL_EVENT_TERMINATED events,
* delegating actual handling of those events to guac_rdpsnd_process_connect()
* and guac_rdpsnd_process_terminate() respectively.
*
* The FreeRDP requirements for this function follow those of the
* VirtualChannelInitEventEx callback defined within Microsoft's RDP API:
*
* https://docs.microsoft.com/en-us/previous-versions/windows/embedded/aa514727%28v%3dmsdn.10%29
*
* @param user_param
* The pointer to arbitrary data originally passed via the first parameter
* of the pVirtualChannelInitEx() function call when the associated channel
* was initialized. The pVirtualChannelInitEx() function is exposed within
* the channel entry points structure.
*
* @param init_handle
* The handle which identifies the client connection, typically referred to
* within the FreeRDP source as pInitHandle.
*
* @param event
* An integer representing the event that should be handled. This will be
* either CHANNEL_EVENT_CONNECTED, CHANNEL_EVENT_DISCONNECTED,
* CHANNEL_EVENT_INITIALIZED, CHANNEL_EVENT_TERMINATED, or
* CHANNEL_EVENT_V1_CONNECTED.
*
* @param data
* NULL in all cases except the CHANNEL_EVENT_CONNECTED event, in which
* case this is a null-terminated string containing the name of the server.
*
* @param data_length
* The number of bytes of data, if any.
*/
static VOID guac_rdpsnd_handle_init_event(LPVOID user_param,
LPVOID init_handle, UINT event, LPVOID data, UINT data_length) {
guac_rdpsnd* rdpsnd = (guac_rdpsnd*) user_param;
/* Validate relevant handle matches that of the RDPSND channel */
if (init_handle != rdpsnd->init_handle) {
guac_client_log(rdpsnd->client, GUAC_LOG_WARNING, "An init event "
"(#%i) for the RDPSND channel has been dropped because the "
"relevant init handle (0x%X) does not match the init handle "
"of the RDPSND channel (0x%X).", event, init_handle,
rdpsnd->init_handle);
return;
}
switch (event) {
/* The RDPSND channel has been connected */
case CHANNEL_EVENT_CONNECTED:
guac_rdpsnd_process_connect(rdpsnd);
break;
/* The RDPSND channel has disconnected and now must be cleaned up */
case CHANNEL_EVENT_TERMINATED:
guac_rdpsnd_process_terminate(rdpsnd);
break;
}
}
/**
* Entry point for FreeRDP plugins. This function is automatically invoked when
* the plugin is loaded.
*
* @param entry_points
* Functions and data specific to the FreeRDP side of the virtual channel
* and plugin. This structure must be copied within implementation-specific
* storage such that the functions it references can be invoked when
* needed.
*
* @param init_handle
* The handle which identifies the client connection, typically referred to
* within the FreeRDP source as pInitHandle. This handle is also provided
* to the channel init event handler. The handle must eventually be used
* within the channel open event handler to obtain a handle to the channel
* itself.
*
* @return
* TRUE if the plugin has initialized successfully, FALSE otherwise.
*/
BOOL VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS entry_points,
PVOID init_handle) {
CHANNEL_ENTRY_POINTS_FREERDP_EX* entry_points_ex =
(CHANNEL_ENTRY_POINTS_FREERDP_EX*) entry_points;
/* Allocate plugin */
guac_rdpsnd* rdpsnd = (guac_rdpsnd*) calloc(1, sizeof(guac_rdpsnd));
/* Init channel def */
strcpy(rdpsnd->channel_def.name, "rdpsnd");
rdpsnd->channel_def.options = CHANNEL_OPTION_INITIALIZED
| CHANNEL_OPTION_ENCRYPT_RDP;
/* Maintain reference to associated guac_client */
rdpsnd->client = (guac_client*) entry_points_ex->pExtendedData;
/* Copy FreeRDP data into RDPSND structure for future reference */
rdpsnd->entry_points = *entry_points_ex;
rdpsnd->init_handle = init_handle;
/* Complete initialization */
if (rdpsnd->entry_points.pVirtualChannelInitEx(rdpsnd, rdpsnd, init_handle,
&rdpsnd->channel_def, 1, VIRTUAL_CHANNEL_VERSION_WIN2000,
guac_rdpsnd_handle_init_event) != CHANNEL_RC_OK) {
return FALSE;
}
return TRUE;
}

View File

@ -18,14 +18,15 @@
*/
#ifndef __GUAC_RDPSND_SERVICE_H
#define __GUAC_RDPSND_SERVICE_H
#ifndef GUAC_RDPSND_SERVICE_H
#define GUAC_RDPSND_SERVICE_H
#include "config.h"
#include <freerdp/utils/svc_plugin.h>
#include <freerdp/svc.h>
#include <guacamole/client.h>
#include <winpr/stream.h>
#include <winpr/wtsapi.h>
/**
* The maximum number of PCM formats to accept during the initial RDPSND
@ -62,14 +63,7 @@ typedef struct guac_pcm_format {
* Structure representing the current state of the Guacamole RDPSND plugin for
* FreeRDP.
*/
typedef struct guac_rdpsndPlugin {
/**
* The FreeRDP parts of this plugin. This absolutely MUST be first.
* FreeRDP depends on accessing this structure as if it were an instance
* of rdpSvcPlugin.
*/
rdpSvcPlugin plugin;
typedef struct guac_rdpsnd {
/**
* The Guacamole client associated with the guac_audio_stream that this
@ -77,6 +71,38 @@ typedef struct guac_rdpsndPlugin {
*/
guac_client* client;
/**
* The definition of this virtual channel (RDPSND).
*/
CHANNEL_DEF channel_def;
/**
* Functions and data specific to the FreeRDP side of the virtual channel
* and plugin.
*/
CHANNEL_ENTRY_POINTS_FREERDP_EX entry_points;
/**
* Handle which identifies the client connection, typically referred to
* within the FreeRDP source as pInitHandle. This handle is provided to the
* channel entry point and the channel init event handler. The handle must
* eventually be used within the channel open event handler to obtain a
* handle to the channel itself.
*/
PVOID init_handle;
/**
* Handle which identifies the channel itself, typically referred to within
* the FreeRDP source as OpenHandle. This handle is obtained through a call
* to entry_points.pVirtualChannelOpenEx() in response to receiving a
* CHANNEL_EVENT_CONNECTED event via the init event handler.
*
* Data is received in CHANNEL_EVENT_DATA_RECEIVED events via the open
* event handler, and data is written through calls to
* entry_points.pVirtualChannelWriteEx().
*/
DWORD open_handle;
/**
* The block number of the last SNDC_WAVE (WaveInfo) PDU received.
*/
@ -118,29 +144,7 @@ typedef struct guac_rdpsndPlugin {
*/
int format_count;
} guac_rdpsndPlugin;
/**
* Handler called when this plugin is loaded by FreeRDP.
*/
void guac_rdpsnd_process_connect(rdpSvcPlugin* plugin);
/**
* Handler called when this plugin receives data along its designated channel.
*/
void guac_rdpsnd_process_receive(rdpSvcPlugin* plugin,
wStream* input_stream);
/**
* Handler called when this plugin is being unloaded.
*/
void guac_rdpsnd_process_terminate(rdpSvcPlugin* plugin);
/**
* Handler called when this plugin receives an event. For the sake of RDPSND,
* all events will be ignored and simply free'd.
*/
void guac_rdpsnd_process_event(rdpSvcPlugin* plugin, wMessage* event);
} guac_rdpsnd;
#endif