GUACAMOLE-249: Restructure audio input such that audio buffer can be separately linked.
On some platforms, the libguacai-client.so plugin for FreeRDP reports an unlinked symbol: undefined symbol: guac_freerdp_dynamic_channel_collection_add (/usr/local/lib/freerdp2/libguacai-client.so) This symbol is actually unused within the plugin, but may be referenced due to being defined within a function in a common piece of source shared between the plugin and the RDP support. Separating the actual common components such that they can be included by both the RDP support and the libguacai-client.so plugin removes the potential for unused pieces being flagged as missing.
This commit is contained in:
parent
e9846945c7
commit
4282da662f
@ -39,7 +39,8 @@ nodist_libguac_client_rdp_la_SOURCES = \
|
||||
|
||||
libguac_client_rdp_la_SOURCES = \
|
||||
bitmap.c \
|
||||
channels/audio-input.c \
|
||||
channels/audio-input/audio-buffer.c \
|
||||
channels/audio-input/audio-input.c \
|
||||
channels/cliprdr.c \
|
||||
channels/common-svc.c \
|
||||
channels/disp.c \
|
||||
@ -81,7 +82,8 @@ libguac_client_rdp_la_SOURCES = \
|
||||
|
||||
noinst_HEADERS = \
|
||||
bitmap.h \
|
||||
channels/audio-input.h \
|
||||
channels/audio-input/audio-buffer.h \
|
||||
channels/audio-input/audio-input.h \
|
||||
channels/cliprdr.h \
|
||||
channels/common-svc.h \
|
||||
channels/disp.h \
|
||||
@ -173,10 +175,10 @@ libguac_common_svc_client_la_LIBADD = \
|
||||
# Audio Input
|
||||
#
|
||||
|
||||
libguacai_client_la_SOURCES = \
|
||||
channels/audio-input.c \
|
||||
plugins/guacai/guacai-messages.c \
|
||||
plugins/guacai/guacai.c \
|
||||
libguacai_client_la_SOURCES = \
|
||||
channels/audio-input/audio-buffer.c \
|
||||
plugins/guacai/guacai-messages.c \
|
||||
plugins/guacai/guacai.c \
|
||||
plugins/ptr-string.c
|
||||
|
||||
libguacai_client_la_CFLAGS = \
|
||||
|
@ -17,12 +17,9 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "channels/audio-input.h"
|
||||
#include "plugins/channels.h"
|
||||
#include "plugins/ptr-string.h"
|
||||
#include "channels/audio-input/audio-buffer.h"
|
||||
#include "rdp.h"
|
||||
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <guacamole/client.h>
|
||||
#include <guacamole/protocol.h>
|
||||
#include <guacamole/socket.h>
|
||||
@ -30,169 +27,9 @@
|
||||
#include <guacamole/user.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* Parses the given raw audio mimetype, producing the corresponding rate,
|
||||
* number of channels, and bytes per sample.
|
||||
*
|
||||
* @param mimetype
|
||||
* The raw auduio mimetype to parse.
|
||||
*
|
||||
* @param rate
|
||||
* A pointer to an int where the sample rate for the PCM format described
|
||||
* by the given mimetype should be stored.
|
||||
*
|
||||
* @param channels
|
||||
* A pointer to an int where the number of channels used by the PCM format
|
||||
* described by the given mimetype should be stored.
|
||||
*
|
||||
* @param bps
|
||||
* A pointer to an int where the number of bytes used the PCM format for
|
||||
* each sample (independent of number of channels) described by the given
|
||||
* mimetype should be stored.
|
||||
*
|
||||
* @return
|
||||
* Zero if the given mimetype is a raw audio mimetype and has been parsed
|
||||
* successfully, non-zero otherwise.
|
||||
*/
|
||||
static int guac_rdp_audio_parse_mimetype(const char* mimetype,
|
||||
int* rate, int* channels, int* bps) {
|
||||
|
||||
int parsed_rate = -1;
|
||||
int parsed_channels = 1;
|
||||
int parsed_bps;
|
||||
|
||||
/* PCM audio with one byte per sample */
|
||||
if (strncmp(mimetype, "audio/L8;", 9) == 0) {
|
||||
mimetype += 8; /* Advance to semicolon ONLY */
|
||||
parsed_bps = 1;
|
||||
}
|
||||
|
||||
/* PCM audio with two bytes per sample */
|
||||
else if (strncmp(mimetype, "audio/L16;", 10) == 0) {
|
||||
mimetype += 9; /* Advance to semicolon ONLY */
|
||||
parsed_bps = 2;
|
||||
}
|
||||
|
||||
/* Unsupported mimetype */
|
||||
else
|
||||
return 1;
|
||||
|
||||
/* Parse each parameter name/value pair within the mimetype */
|
||||
do {
|
||||
|
||||
/* Advance to first character of parameter (current is either a
|
||||
* semicolon or a comma) */
|
||||
mimetype++;
|
||||
|
||||
/* Parse number of channels */
|
||||
if (strncmp(mimetype, "channels=", 9) == 0) {
|
||||
|
||||
mimetype += 9;
|
||||
parsed_channels = strtol(mimetype, (char**) &mimetype, 10);
|
||||
|
||||
/* Fail if value invalid / out of range */
|
||||
if (errno == EINVAL || errno == ERANGE)
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
/* Parse number of rate */
|
||||
else if (strncmp(mimetype, "rate=", 5) == 0) {
|
||||
|
||||
mimetype += 5;
|
||||
parsed_rate = strtol(mimetype, (char**) &mimetype, 10);
|
||||
|
||||
/* Fail if value invalid / out of range */
|
||||
if (errno == EINVAL || errno == ERANGE)
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
/* Advance to next parameter */
|
||||
mimetype = strchr(mimetype, ',');
|
||||
|
||||
} while (mimetype != NULL);
|
||||
|
||||
/* Mimetype is invalid if rate was not specified */
|
||||
if (parsed_rate == -1)
|
||||
return 1;
|
||||
|
||||
/* Parse success */
|
||||
*rate = parsed_rate;
|
||||
*channels = parsed_channels;
|
||||
*bps = parsed_bps;
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
int guac_rdp_audio_handler(guac_user* user, guac_stream* stream,
|
||||
char* mimetype) {
|
||||
|
||||
guac_client* client = user->client;
|
||||
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
||||
|
||||
int rate;
|
||||
int channels;
|
||||
int bps;
|
||||
|
||||
/* Parse mimetype, abort on parse error */
|
||||
if (guac_rdp_audio_parse_mimetype(mimetype, &rate, &channels, &bps)) {
|
||||
guac_user_log(user, GUAC_LOG_WARNING, "Denying user audio stream with "
|
||||
"unsupported mimetype: \"%s\"", mimetype);
|
||||
guac_protocol_send_ack(user->socket, stream, "Unsupported audio "
|
||||
"mimetype", GUAC_PROTOCOL_STATUS_CLIENT_BAD_TYPE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Init stream data */
|
||||
stream->blob_handler = guac_rdp_audio_blob_handler;
|
||||
stream->end_handler = guac_rdp_audio_end_handler;
|
||||
|
||||
/* Associate stream with audio buffer */
|
||||
guac_rdp_audio_buffer_set_stream(rdp_client->audio_input, user, stream,
|
||||
rate, channels, bps);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
int guac_rdp_audio_blob_handler(guac_user* user, guac_stream* stream,
|
||||
void* data, int length) {
|
||||
|
||||
guac_client* client = user->client;
|
||||
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
||||
|
||||
/* Write blob to audio stream, buffering if necessary */
|
||||
guac_rdp_audio_buffer_write(rdp_client->audio_input, data, length);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
int guac_rdp_audio_end_handler(guac_user* user, guac_stream* stream) {
|
||||
|
||||
/* Ignore - the AUDIO_INPUT channel will simply not receive anything */
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
void guac_rdp_audio_load_plugin(rdpContext* context) {
|
||||
|
||||
guac_client* client = ((rdp_freerdp_context*) context)->client;
|
||||
char client_ref[GUAC_RDP_PTR_STRING_LENGTH];
|
||||
|
||||
/* Add "AUDIO_INPUT" channel */
|
||||
guac_rdp_ptr_to_string(client, client_ref);
|
||||
guac_freerdp_dynamic_channel_collection_add(context->settings, "guacai", client_ref, NULL);
|
||||
|
||||
}
|
||||
|
||||
guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc() {
|
||||
guac_rdp_audio_buffer* buffer = calloc(1, sizeof(guac_rdp_audio_buffer));
|
@ -17,10 +17,9 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#ifndef GUAC_RDP_CHANNELS_AUDIO_INPUT_H
|
||||
#define GUAC_RDP_CHANNELS_AUDIO_INPUT_H
|
||||
#ifndef GUAC_RDP_CHANNELS_AUDIO_INPUT_AUDIO_BUFFER_H
|
||||
#define GUAC_RDP_CHANNELS_AUDIO_INPUT_AUDIO_BUFFER_H
|
||||
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <guacamole/stream.h>
|
||||
#include <guacamole/user.h>
|
||||
#include <pthread.h>
|
||||
@ -274,31 +273,5 @@ void guac_rdp_audio_buffer_end(guac_rdp_audio_buffer* audio_buffer);
|
||||
*/
|
||||
void guac_rdp_audio_buffer_free(guac_rdp_audio_buffer* audio_buffer);
|
||||
|
||||
/**
|
||||
* Handler for inbound audio data (audio input).
|
||||
*/
|
||||
guac_user_audio_handler guac_rdp_audio_handler;
|
||||
|
||||
/**
|
||||
* Handler for stream data related to audio input.
|
||||
*/
|
||||
guac_user_blob_handler guac_rdp_audio_blob_handler;
|
||||
|
||||
/**
|
||||
* Handler for end-of-stream related to audio input.
|
||||
*/
|
||||
guac_user_end_handler guac_rdp_audio_end_handler;
|
||||
|
||||
/**
|
||||
* Adds Guacamole's "guacai" plugin to the list of dynamic virtual channel
|
||||
* plugins to be loaded by FreeRDP's "drdynvc" plugin. The plugin will only
|
||||
* be loaded once the "drdynvc" plugin is loaded. The "guacai" plugin
|
||||
* ultimately adds support for the "AUDIO_INPUT" dynamic virtual channel.
|
||||
*
|
||||
* @param context
|
||||
* The rdpContext associated with the active RDP session.
|
||||
*/
|
||||
void guac_rdp_audio_load_plugin(rdpContext* context);
|
||||
|
||||
#endif
|
||||
|
195
src/protocols/rdp/channels/audio-input/audio-input.c
Normal file
195
src/protocols/rdp/channels/audio-input/audio-input.c
Normal file
@ -0,0 +1,195 @@
|
||||
/*
|
||||
* 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 "channels/audio-input/audio-input.h"
|
||||
#include "plugins/channels.h"
|
||||
#include "plugins/ptr-string.h"
|
||||
#include "rdp.h"
|
||||
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <guacamole/client.h>
|
||||
#include <guacamole/protocol.h>
|
||||
#include <guacamole/socket.h>
|
||||
#include <guacamole/stream.h>
|
||||
#include <guacamole/user.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* Parses the given raw audio mimetype, producing the corresponding rate,
|
||||
* number of channels, and bytes per sample.
|
||||
*
|
||||
* @param mimetype
|
||||
* The raw auduio mimetype to parse.
|
||||
*
|
||||
* @param rate
|
||||
* A pointer to an int where the sample rate for the PCM format described
|
||||
* by the given mimetype should be stored.
|
||||
*
|
||||
* @param channels
|
||||
* A pointer to an int where the number of channels used by the PCM format
|
||||
* described by the given mimetype should be stored.
|
||||
*
|
||||
* @param bps
|
||||
* A pointer to an int where the number of bytes used the PCM format for
|
||||
* each sample (independent of number of channels) described by the given
|
||||
* mimetype should be stored.
|
||||
*
|
||||
* @return
|
||||
* Zero if the given mimetype is a raw audio mimetype and has been parsed
|
||||
* successfully, non-zero otherwise.
|
||||
*/
|
||||
static int guac_rdp_audio_parse_mimetype(const char* mimetype,
|
||||
int* rate, int* channels, int* bps) {
|
||||
|
||||
int parsed_rate = -1;
|
||||
int parsed_channels = 1;
|
||||
int parsed_bps;
|
||||
|
||||
/* PCM audio with one byte per sample */
|
||||
if (strncmp(mimetype, "audio/L8;", 9) == 0) {
|
||||
mimetype += 8; /* Advance to semicolon ONLY */
|
||||
parsed_bps = 1;
|
||||
}
|
||||
|
||||
/* PCM audio with two bytes per sample */
|
||||
else if (strncmp(mimetype, "audio/L16;", 10) == 0) {
|
||||
mimetype += 9; /* Advance to semicolon ONLY */
|
||||
parsed_bps = 2;
|
||||
}
|
||||
|
||||
/* Unsupported mimetype */
|
||||
else
|
||||
return 1;
|
||||
|
||||
/* Parse each parameter name/value pair within the mimetype */
|
||||
do {
|
||||
|
||||
/* Advance to first character of parameter (current is either a
|
||||
* semicolon or a comma) */
|
||||
mimetype++;
|
||||
|
||||
/* Parse number of channels */
|
||||
if (strncmp(mimetype, "channels=", 9) == 0) {
|
||||
|
||||
mimetype += 9;
|
||||
parsed_channels = strtol(mimetype, (char**) &mimetype, 10);
|
||||
|
||||
/* Fail if value invalid / out of range */
|
||||
if (errno == EINVAL || errno == ERANGE)
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
/* Parse number of rate */
|
||||
else if (strncmp(mimetype, "rate=", 5) == 0) {
|
||||
|
||||
mimetype += 5;
|
||||
parsed_rate = strtol(mimetype, (char**) &mimetype, 10);
|
||||
|
||||
/* Fail if value invalid / out of range */
|
||||
if (errno == EINVAL || errno == ERANGE)
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
/* Advance to next parameter */
|
||||
mimetype = strchr(mimetype, ',');
|
||||
|
||||
} while (mimetype != NULL);
|
||||
|
||||
/* Mimetype is invalid if rate was not specified */
|
||||
if (parsed_rate == -1)
|
||||
return 1;
|
||||
|
||||
/* Parse success */
|
||||
*rate = parsed_rate;
|
||||
*channels = parsed_channels;
|
||||
*bps = parsed_bps;
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
int guac_rdp_audio_handler(guac_user* user, guac_stream* stream,
|
||||
char* mimetype) {
|
||||
|
||||
guac_client* client = user->client;
|
||||
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
||||
|
||||
int rate;
|
||||
int channels;
|
||||
int bps;
|
||||
|
||||
/* Parse mimetype, abort on parse error */
|
||||
if (guac_rdp_audio_parse_mimetype(mimetype, &rate, &channels, &bps)) {
|
||||
guac_user_log(user, GUAC_LOG_WARNING, "Denying user audio stream with "
|
||||
"unsupported mimetype: \"%s\"", mimetype);
|
||||
guac_protocol_send_ack(user->socket, stream, "Unsupported audio "
|
||||
"mimetype", GUAC_PROTOCOL_STATUS_CLIENT_BAD_TYPE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Init stream data */
|
||||
stream->blob_handler = guac_rdp_audio_blob_handler;
|
||||
stream->end_handler = guac_rdp_audio_end_handler;
|
||||
|
||||
/* Associate stream with audio buffer */
|
||||
guac_rdp_audio_buffer_set_stream(rdp_client->audio_input, user, stream,
|
||||
rate, channels, bps);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
int guac_rdp_audio_blob_handler(guac_user* user, guac_stream* stream,
|
||||
void* data, int length) {
|
||||
|
||||
guac_client* client = user->client;
|
||||
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
||||
|
||||
/* Write blob to audio stream, buffering if necessary */
|
||||
guac_rdp_audio_buffer_write(rdp_client->audio_input, data, length);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
int guac_rdp_audio_end_handler(guac_user* user, guac_stream* stream) {
|
||||
|
||||
/* Ignore - the AUDIO_INPUT channel will simply not receive anything */
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
void guac_rdp_audio_load_plugin(rdpContext* context) {
|
||||
|
||||
guac_client* client = ((rdp_freerdp_context*) context)->client;
|
||||
char client_ref[GUAC_RDP_PTR_STRING_LENGTH];
|
||||
|
||||
/* Add "AUDIO_INPUT" channel */
|
||||
guac_rdp_ptr_to_string(client, client_ref);
|
||||
guac_freerdp_dynamic_channel_collection_add(context->settings, "guacai", client_ref, NULL);
|
||||
|
||||
}
|
||||
|
53
src/protocols/rdp/channels/audio-input/audio-input.h
Normal file
53
src/protocols/rdp/channels/audio-input/audio-input.h
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef GUAC_RDP_CHANNELS_AUDIO_INPUT_H
|
||||
#define GUAC_RDP_CHANNELS_AUDIO_INPUT_H
|
||||
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <guacamole/user.h>
|
||||
|
||||
/**
|
||||
* Handler for inbound audio data (audio input).
|
||||
*/
|
||||
guac_user_audio_handler guac_rdp_audio_handler;
|
||||
|
||||
/**
|
||||
* Handler for stream data related to audio input.
|
||||
*/
|
||||
guac_user_blob_handler guac_rdp_audio_blob_handler;
|
||||
|
||||
/**
|
||||
* Handler for end-of-stream related to audio input.
|
||||
*/
|
||||
guac_user_end_handler guac_rdp_audio_end_handler;
|
||||
|
||||
/**
|
||||
* Adds Guacamole's "guacai" plugin to the list of dynamic virtual channel
|
||||
* plugins to be loaded by FreeRDP's "drdynvc" plugin. The plugin will only
|
||||
* be loaded once the "drdynvc" plugin is loaded. The "guacai" plugin
|
||||
* ultimately adds support for the "AUDIO_INPUT" dynamic virtual channel.
|
||||
*
|
||||
* @param context
|
||||
* The rdpContext associated with the active RDP session.
|
||||
*/
|
||||
void guac_rdp_audio_load_plugin(rdpContext* context);
|
||||
|
||||
#endif
|
||||
|
@ -18,7 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "client.h"
|
||||
#include "channels/audio-input.h"
|
||||
#include "channels/audio-input/audio-buffer.h"
|
||||
#include "channels/cliprdr.h"
|
||||
#include "channels/disp.h"
|
||||
#include "common/recording.h"
|
||||
|
@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "channels/audio-input.h"
|
||||
#include "channels/audio-input/audio-buffer.h"
|
||||
#include "plugins/guacai/guacai-messages.h"
|
||||
#include "rdp.h"
|
||||
|
||||
|
@ -20,7 +20,7 @@
|
||||
#ifndef GUAC_RDP_PLUGINS_GUACAI_MESSAGES_H
|
||||
#define GUAC_RDP_PLUGINS_GUACAI_MESSAGES_H
|
||||
|
||||
#include "channels/audio-input.h"
|
||||
#include "channels/audio-input/audio-buffer.h"
|
||||
|
||||
#include <freerdp/dvc.h>
|
||||
#include <guacamole/client.h>
|
||||
|
@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "channels/audio-input.h"
|
||||
#include "channels/audio-input/audio-buffer.h"
|
||||
#include "plugins/guacai/guacai.h"
|
||||
#include "plugins/guacai/guacai-messages.h"
|
||||
#include "plugins/ptr-string.h"
|
||||
|
@ -18,7 +18,8 @@
|
||||
*/
|
||||
|
||||
#include "bitmap.h"
|
||||
#include "channels/audio-input.h"
|
||||
#include "channels/audio-input/audio-buffer.h"
|
||||
#include "channels/audio-input/audio-input.h"
|
||||
#include "channels/cliprdr.h"
|
||||
#include "channels/disp.h"
|
||||
#include "channels/pipe-svc.h"
|
||||
|
@ -20,7 +20,7 @@
|
||||
#ifndef GUAC_RDP_H
|
||||
#define GUAC_RDP_H
|
||||
|
||||
#include "channels/audio-input.h"
|
||||
#include "channels/audio-input/audio-buffer.h"
|
||||
#include "channels/cliprdr.h"
|
||||
#include "channels/disp.h"
|
||||
#include "common/clipboard.h"
|
||||
|
@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "channels/audio-input.h"
|
||||
#include "channels/audio-input/audio-input.h"
|
||||
#include "channels/cliprdr.h"
|
||||
#include "channels/pipe-svc.h"
|
||||
#include "common/cursor.h"
|
||||
|
Loading…
Reference in New Issue
Block a user