| /* |
| * 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/audio-input/audio-buffer.h" |
| #include "plugins/guacai/guacai.h" |
| #include "plugins/guacai/guacai-messages.h" |
| #include "plugins/ptr-string.h" |
| #include "rdp.h" |
| |
| #include <freerdp/dvc.h> |
| #include <freerdp/settings.h> |
| #include <guacamole/client.h> |
| #include <winpr/stream.h> |
| #include <winpr/wtsapi.h> |
| #include <winpr/wtypes.h> |
| |
| #include <stdlib.h> |
| |
| /** |
| * Handles the given data received along the AUDIO_INPUT channel of the RDP |
| * connection associated with the given guac_client. This handler is |
| * API-independent and is invoked by API-dependent guac_rdp_ai_data callback |
| * specific to the version of FreeRDP installed. |
| * |
| * @param client |
| * The guac_client associated with RDP connection having the AUDIO_INPUT |
| * connection along which the given data was received. |
| * |
| * @param channel |
| * A reference to the IWTSVirtualChannel instance along which responses |
| * should be sent. |
| * |
| * @param stream |
| * The data received along the AUDIO_INPUT channel. |
| */ |
| static void guac_rdp_ai_handle_data(guac_client* client, |
| IWTSVirtualChannel* channel, wStream* stream) { |
| |
| if (Stream_GetRemainingLength(stream) < 1) |
| return; |
| |
| /* Read message ID from received PDU */ |
| BYTE message_id; |
| Stream_Read_UINT8(stream, message_id); |
| |
| /* Invoke appropriate message processor based on ID */ |
| switch (message_id) { |
| |
| /* Version PDU */ |
| case GUAC_RDP_MSG_SNDIN_VERSION: |
| guac_rdp_ai_process_version(client, channel, stream); |
| break; |
| |
| /* Sound Formats PDU */ |
| case GUAC_RDP_MSG_SNDIN_FORMATS: |
| guac_rdp_ai_process_formats(client, channel, stream); |
| break; |
| |
| /* Open PDU */ |
| case GUAC_RDP_MSG_SNDIN_OPEN: |
| guac_rdp_ai_process_open(client, channel, stream); |
| break; |
| |
| /* Format Change PDU */ |
| case GUAC_RDP_MSG_SNDIN_FORMATCHANGE: |
| guac_rdp_ai_process_formatchange(client, channel, stream); |
| break; |
| |
| /* Log unknown message IDs */ |
| default: |
| guac_client_log(client, GUAC_LOG_DEBUG, |
| "Unknown AUDIO_INPUT message ID: 0x%x", message_id); |
| |
| } |
| |
| } |
| |
| /** |
| * Callback which is invoked when data is received along a connection to the |
| * AUDIO_INPUT plugin. |
| * |
| * @param channel_callback |
| * The IWTSVirtualChannelCallback structure to which this callback was |
| * originally assigned. |
| * |
| * @param stream |
| * The data received. |
| * |
| * @return |
| * Always zero. |
| */ |
| static UINT guac_rdp_ai_data(IWTSVirtualChannelCallback* channel_callback, |
| wStream* stream) { |
| |
| guac_rdp_ai_channel_callback* ai_channel_callback = |
| (guac_rdp_ai_channel_callback*) channel_callback; |
| IWTSVirtualChannel* channel = ai_channel_callback->channel; |
| |
| /* Invoke generalized (API-independent) data handler */ |
| guac_rdp_ai_handle_data(ai_channel_callback->client, channel, stream); |
| |
| return CHANNEL_RC_OK; |
| |
| } |
| |
| /** |
| * Callback which is invoked when a connection to the AUDIO_INPUT plugin is |
| * closed. |
| * |
| * @param channel_callback |
| * The IWTSVirtualChannelCallback structure to which this callback was |
| * originally assigned. |
| * |
| * @return |
| * Always zero. |
| */ |
| static UINT guac_rdp_ai_close(IWTSVirtualChannelCallback* channel_callback) { |
| |
| guac_rdp_ai_channel_callback* ai_channel_callback = |
| (guac_rdp_ai_channel_callback*) channel_callback; |
| |
| guac_client* client = ai_channel_callback->client; |
| guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; |
| guac_rdp_audio_buffer* audio_buffer = rdp_client->audio_input; |
| |
| /* Log closure of AUDIO_INPUT channel */ |
| guac_client_log(client, GUAC_LOG_DEBUG, |
| "AUDIO_INPUT channel connection closed"); |
| |
| guac_rdp_audio_buffer_end(audio_buffer); |
| free(ai_channel_callback); |
| return CHANNEL_RC_OK; |
| |
| } |
| |
| /** |
| * Callback which is invoked when a new connection is received by the |
| * AUDIO_INPUT plugin. Additional callbacks required to handle received data |
| * and closure of the connection must be installed at this point. |
| * |
| * @param listener_callback |
| * The IWTSListenerCallback structure associated with the AUDIO_INPUT |
| * plugin receiving the new connection. |
| * |
| * @param channel |
| * A reference to the IWTSVirtualChannel instance along which data related |
| * to the AUDIO_INPUT channel should be sent. |
| * |
| * @param data |
| * Absolutely no idea. According to Microsoft's documentation for the |
| * function prototype on which FreeRDP's API appears to be based: "This |
| * parameter is not implemented and is reserved for future use." |
| * |
| * @param accept |
| * Pointer to a flag which should be set to TRUE if the connection should |
| * be accepted or FALSE otherwise. In the case of FreeRDP, this value |
| * defaults to TRUE, and TRUE absolutely MUST be identically 1 or it will |
| * be interpreted as FALSE. |
| * |
| * @param channel_callback |
| * A pointer to the location that the new IWTSVirtualChannelCallback |
| * structure containing the required callbacks should be assigned. |
| * |
| * @return |
| * Always zero. |
| */ |
| static UINT guac_rdp_ai_new_connection( |
| IWTSListenerCallback* listener_callback, IWTSVirtualChannel* channel, |
| BYTE* data, int* accept, |
| IWTSVirtualChannelCallback** channel_callback) { |
| |
| guac_rdp_ai_listener_callback* ai_listener_callback = |
| (guac_rdp_ai_listener_callback*) listener_callback; |
| |
| /* Log new AUDIO_INPUT connection */ |
| guac_client_log(ai_listener_callback->client, GUAC_LOG_DEBUG, |
| "New AUDIO_INPUT channel connection"); |
| |
| /* Allocate new channel callback */ |
| guac_rdp_ai_channel_callback* ai_channel_callback = |
| calloc(1, sizeof(guac_rdp_ai_channel_callback)); |
| |
| /* Init listener callback with data from plugin */ |
| ai_channel_callback->client = ai_listener_callback->client; |
| ai_channel_callback->channel = channel; |
| ai_channel_callback->parent.OnDataReceived = guac_rdp_ai_data; |
| ai_channel_callback->parent.OnClose = guac_rdp_ai_close; |
| |
| /* Return callback through pointer */ |
| *channel_callback = (IWTSVirtualChannelCallback*) ai_channel_callback; |
| |
| return CHANNEL_RC_OK; |
| |
| } |
| |
| /** |
| * Callback which is invoked when the AUDIO_INPUT plugin has been loaded and |
| * needs to be initialized with other callbacks and data. |
| * |
| * @param plugin |
| * The AUDIO_INPUT plugin that needs to be initialied. |
| * |
| * @param manager |
| * The IWTSVirtualChannelManager instance with which the AUDIO_INPUT plugin |
| * must be registered. |
| * |
| * @return |
| * Always zero. |
| */ |
| static UINT guac_rdp_ai_initialize(IWTSPlugin* plugin, |
| IWTSVirtualChannelManager* manager) { |
| |
| /* Allocate new listener callback */ |
| guac_rdp_ai_listener_callback* ai_listener_callback = |
| calloc(1, sizeof(guac_rdp_ai_listener_callback)); |
| |
| /* Ensure listener callback is freed when plugin is terminated */ |
| guac_rdp_ai_plugin* ai_plugin = (guac_rdp_ai_plugin*) plugin; |
| ai_plugin->listener_callback = ai_listener_callback; |
| |
| /* Init listener callback with data from plugin */ |
| ai_listener_callback->client = ai_plugin->client; |
| ai_listener_callback->parent.OnNewChannelConnection = |
| guac_rdp_ai_new_connection; |
| |
| /* Register listener for "AUDIO_INPUT" channel */ |
| manager->CreateListener(manager, "AUDIO_INPUT", 0, |
| (IWTSListenerCallback*) ai_listener_callback, NULL); |
| |
| return CHANNEL_RC_OK; |
| |
| } |
| |
| /** |
| * Callback which is invoked when all connections to the AUDIO_INPUT plugin |
| * have closed and the plugin is being unloaded. |
| * |
| * @param plugin |
| * The AUDIO_INPUT plugin being unloaded. |
| * |
| * @return |
| * Always zero. |
| */ |
| static UINT guac_rdp_ai_terminated(IWTSPlugin* plugin) { |
| |
| guac_rdp_ai_plugin* ai_plugin = (guac_rdp_ai_plugin*) plugin; |
| guac_client* client = ai_plugin->client; |
| |
| /* Free all non-FreeRDP data */ |
| free(ai_plugin->listener_callback); |
| free(ai_plugin); |
| |
| guac_client_log(client, GUAC_LOG_DEBUG, "AUDIO_INPUT plugin unloaded."); |
| return CHANNEL_RC_OK; |
| |
| } |
| |
| /** |
| * Entry point for AUDIO_INPUT dynamic virtual channel. |
| */ |
| int DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) { |
| |
| /* Pull guac_client from arguments */ |
| ADDIN_ARGV* args = pEntryPoints->GetPluginData(pEntryPoints); |
| guac_client* client = (guac_client*) guac_rdp_string_to_ptr(args->argv[1]); |
| |
| /* Pull previously-allocated plugin */ |
| guac_rdp_ai_plugin* ai_plugin = (guac_rdp_ai_plugin*) |
| pEntryPoints->GetPlugin(pEntryPoints, "guacai"); |
| |
| /* If no such plugin allocated, allocate and register it now */ |
| if (ai_plugin == NULL) { |
| |
| /* Init plugin callbacks and data */ |
| ai_plugin = calloc(1, sizeof(guac_rdp_ai_plugin)); |
| ai_plugin->parent.Initialize = guac_rdp_ai_initialize; |
| ai_plugin->parent.Terminated = guac_rdp_ai_terminated; |
| ai_plugin->client = client; |
| |
| /* Register plugin as "guacai" for later retrieval */ |
| pEntryPoints->RegisterPlugin(pEntryPoints, "guacai", |
| (IWTSPlugin*) ai_plugin); |
| |
| guac_client_log(client, GUAC_LOG_DEBUG, "AUDIO_INPUT plugin loaded."); |
| } |
| |
| return CHANNEL_RC_OK; |
| |
| } |
| |