Merge 1.1.0 changes back to master.
diff --git a/configure.ac b/configure.ac
index ea98b8a..dba5f71 100644
--- a/configure.ac
+++ b/configure.ac
@@ -605,6 +605,30 @@
fi
+# Variation in memory internal allocation/free behavior
+if test "x${have_freerdp2}" = "xyes"
+then
+
+ # FreeRDP 2.0.0-rc0 and older automatically free rdpBitmap and its
+ # associated data member within Bitmap_Free(), relying on the
+ # implementation-specific free handler to free only implementation-specific
+ # data. This changed in commit 2cf10cc, and implementations must now
+ # manually free all data associated with the rdpBitmap, even data which
+ # was not allocated by the implementation.
+ AC_MSG_CHECKING([whether Bitmap_Free() frees the rdpBitmap and its image data])
+ AC_EGREP_CPP([\"2\\.0\\.0-dev\"], [
+
+ #include <freerdp/version.h>
+ FREERDP_VERSION_FULL
+
+ ],
+ [AC_MSG_RESULT([yes])]
+ [AC_DEFINE([FREERDP_BITMAP_FREE_FREES_BITMAP],,
+ [Whether Bitmap_Free() frees the rdpBitmap and its image data])],
+ [AC_MSG_RESULT([no])])
+
+fi
+
# Glyph callback variants
if test "x${have_freerdp2}" = "xyes"
then
diff --git a/src/libguac/audio.c b/src/libguac/audio.c
index cc577c7..0f6a6aa 100644
--- a/src/libguac/audio.c
+++ b/src/libguac/audio.c
@@ -117,6 +117,12 @@
audio->client = client;
audio->stream = guac_client_alloc_stream(client);
+ /* Abort allocation if underlying stream cannot be allocated */
+ if (audio->stream == NULL) {
+ free(audio);
+ return NULL;
+ }
+
/* Load PCM properties */
audio->rate = rate;
audio->channels = channels;
@@ -188,6 +194,9 @@
if (audio->encoder != NULL && audio->encoder->end_handler)
audio->encoder->end_handler(audio);
+ /* Release stream back to client pool */
+ guac_client_free_stream(audio->client, audio->stream);
+
/* Free associated data */
free(audio);
diff --git a/src/libguac/guacamole/audio.h b/src/libguac/guacamole/audio.h
index df41772..2f030ec 100644
--- a/src/libguac/guacamole/audio.h
+++ b/src/libguac/guacamole/audio.h
@@ -148,7 +148,8 @@
* @return
* The newly allocated guac_audio_stream, or NULL if no audio stream could
* be allocated due to lack of support on the part of the connecting
- * Guacamole client.
+ * Guacamole client or due to reaching the maximum number of active
+ * streams.
*/
guac_audio_stream* guac_audio_stream_alloc(guac_client* client,
guac_audio_encoder* encoder, int rate, int channels, int bps);
diff --git a/src/libguac/guacamole/client.h b/src/libguac/guacamole/client.h
index 88d1f41..71d48f2 100644
--- a/src/libguac/guacamole/client.h
+++ b/src/libguac/guacamole/client.h
@@ -383,7 +383,8 @@
* The client to allocate the stream for.
*
* @return
- * The next available stream, or a newly allocated stream.
+ * The next available stream, or a newly allocated stream, or NULL if the
+ * maximum number of active streams has been reached.
*/
guac_stream* guac_client_alloc_stream(guac_client* client);
diff --git a/src/libguac/guacamole/user.h b/src/libguac/guacamole/user.h
index 702160f..fac42b9 100644
--- a/src/libguac/guacamole/user.h
+++ b/src/libguac/guacamole/user.h
@@ -574,8 +574,12 @@
* Allocates a new stream. An arbitrary index is automatically assigned
* if no previously-allocated stream is available for use.
*
- * @param user The user to allocate the stream for.
- * @return The next available stream, or a newly allocated stream.
+ * @param user
+ * The user to allocate the stream for.
+ *
+ * @return
+ * The next available stream, or a newly allocated stream, or NULL if the
+ * maximum number of active streams has been reached.
*/
guac_stream* guac_user_alloc_stream(guac_user* user);
diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am
index 7bc7e6e..c9f5ae7 100644
--- a/src/protocols/rdp/Makefile.am
+++ b/src/protocols/rdp/Makefile.am
@@ -38,6 +38,7 @@
_generated_keymaps.c
libguac_client_rdp_la_SOURCES = \
+ beep.c \
bitmap.c \
channels/audio-input/audio-buffer.c \
channels/audio-input/audio-input.c \
@@ -81,6 +82,7 @@
user.c
noinst_HEADERS = \
+ beep.h \
bitmap.h \
channels/audio-input/audio-buffer.h \
channels/audio-input/audio-input.h \
diff --git a/src/protocols/rdp/beep.c b/src/protocols/rdp/beep.c
new file mode 100644
index 0000000..8fb5001
--- /dev/null
+++ b/src/protocols/rdp/beep.c
@@ -0,0 +1,147 @@
+/*
+ * 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 "beep.h"
+#include "rdp.h"
+#include "settings.h"
+
+#include <freerdp/freerdp.h>
+#include <guacamole/audio.h>
+#include <guacamole/client.h>
+#include <winpr/wtypes.h>
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+
+/**
+ * Fills the given buffer with signed 8-bit, single-channel PCM at the given
+ * sample rate which will produce a beep of the given frequency.
+ *
+ * @param buffer
+ * The buffer to fill with PCM data.
+ *
+ * @param frequency
+ * The frequency of the beep to generate, in hertz.
+ *
+ * @param rate
+ * The sample rate of the PCM to generate, in samples per second.
+ *
+ * @param buffer_size
+ * The number of bytes of PCM data to write to the given buffer.
+ */
+static void guac_rdp_beep_fill_triangle_wave(unsigned char* buffer,
+ int frequency, int rate, int buffer_size) {
+
+ /* With the distance between each positive/negative peak and zero being the
+ * amplitude, and with the "bounce" between those peaks occurring once
+ * every two periods, the number of distinct states that the triangle wave
+ * function goes through is twice the peak-to-peak amplitude, or four times
+ * the overall amplitude */
+ const int wave_period = GUAC_RDP_BEEP_AMPLITUDE * 4;
+
+ /* With the number of distinct states being the wave_period defined above,
+ * the "bounce" point within that period is half the period */
+ const int wave_bounce_offset = wave_period / 2;
+
+ for (int position = 0; position < buffer_size; position++) {
+
+ /* Calculate relative position within the repeating portion of the wave
+ * (the portion with wave_period unique states) */
+ int wave_position = (position * frequency * wave_period / rate) % wave_period;
+
+ /* Calculate state of the triangle wave function at the calculated
+ * offset, knowing in advance the relative location that the function
+ * should "bounce" */
+ *(buffer++) = abs(wave_position - wave_bounce_offset) - GUAC_RDP_BEEP_AMPLITUDE;
+
+ }
+
+}
+
+/**
+ * Writes PCM data to the given guac_audio_stream which produces a beep of the
+ * given frequency and duration. The provided guac_audio_stream may be
+ * configured for any sample rate but MUST be configured for single-channel,
+ * 8-bit PCM.
+ *
+ * @param audio
+ * The guac_audio_stream which should receive the PCM data.
+ *
+ * @param frequency
+ * The frequency of the beep, in hertz.
+ *
+ * @param duration
+ * The duration of the beep, in milliseconds.
+ */
+static void guac_rdp_beep_write_pcm(guac_audio_stream* audio,
+ int frequency, int duration) {
+
+ int buffer_size = audio->rate * duration / 1000;
+ unsigned char* buffer = malloc(buffer_size);
+
+ /* Beep for given frequency/duration using a simple triangle wave */
+ guac_rdp_beep_fill_triangle_wave(buffer, frequency, audio->rate, buffer_size);
+ guac_audio_stream_write_pcm(audio, buffer, buffer_size);
+
+ free(buffer);
+
+}
+
+BOOL guac_rdp_beep_play_sound(rdpContext* context,
+ const PLAY_SOUND_UPDATE* play_sound) {
+
+ guac_client* client = ((rdp_freerdp_context*) context)->client;
+ guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
+ guac_rdp_settings* settings = rdp_client->settings;
+
+ /* Ignore if audio is not enabled */
+ if (!settings->audio_enabled) {
+ guac_client_log(client, GUAC_LOG_DEBUG, "Ignoring request to beep "
+ "for %" PRIu32 " millseconds at %" PRIu32 " Hz as audio is "
+ "disabled.", play_sound->duration, play_sound->frequency);
+ return TRUE;
+ }
+
+ /* Allocate audio stream which sends audio in a format supported by the
+ * connected client(s) */
+ guac_audio_stream* beep = guac_audio_stream_alloc(client, NULL,
+ GUAC_RDP_BEEP_SAMPLE_RATE, 1, 8);
+
+ /* Stream availability is not guaranteed */
+ if (beep == NULL) {
+ guac_client_log(client, GUAC_LOG_DEBUG, "Ignoring request to beep "
+ "for %" PRIu32 " millseconds at %" PRIu32 " Hz as no audio "
+ "stream could be allocated.", play_sound->duration,
+ play_sound->frequency);
+ return TRUE;
+ }
+
+ /* Limit maximum duration of each beep */
+ int duration = play_sound->duration;
+ if (duration > GUAC_RDP_BEEP_MAX_DURATION)
+ duration = GUAC_RDP_BEEP_MAX_DURATION;
+
+ guac_rdp_beep_write_pcm(beep, play_sound->frequency, duration);
+ guac_audio_stream_free(beep);
+
+ return TRUE;
+
+}
+
diff --git a/src/protocols/rdp/beep.h b/src/protocols/rdp/beep.h
new file mode 100644
index 0000000..8abc064
--- /dev/null
+++ b/src/protocols/rdp/beep.h
@@ -0,0 +1,65 @@
+/*
+ * 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_BEEP_H
+#define GUAC_RDP_BEEP_H
+
+#include <freerdp/freerdp.h>
+#include <winpr/wtypes.h>
+
+/**
+ * The sample rate of the each generated beep, in samples per second.
+ */
+#define GUAC_RDP_BEEP_SAMPLE_RATE 8000
+
+/**
+ * The amplitude (volume) of each beep. As the beep is generated as 8-bit
+ * signed PCM, this should be kept between 0 and 127 inclusive.
+ */
+#define GUAC_RDP_BEEP_AMPLITUDE 64
+
+/**
+ * The maximum duration of each beep, in milliseconds. This value should be
+ * kept relatively small to ensure the amount of data sent for each beep is
+ * minimal.
+ */
+#define GUAC_RDP_BEEP_MAX_DURATION 500
+
+/**
+ * Processes a Play Sound PDU received from the RDP server, beeping for the
+ * requested duration and at the requested frequency. If audio has been
+ * disabled for the connection, the Play Sound PDU will be silently ignored,
+ * and this function has no effect. Beeps in excess of the maximum specified
+ * by GUAC_RDP_BEEP_MAX_DURATION will be truncated.
+ *
+ * @param context
+ * The rdpContext associated with the current RDP session.
+ *
+ * @param play_sound
+ * The PLAY_SOUND_UPDATE structure representing the received Play Sound
+ * PDU.
+ *
+ * @return
+ * TRUE if successful, FALSE otherwise.
+ */
+BOOL guac_rdp_beep_play_sound(rdpContext* context,
+ const PLAY_SOUND_UPDATE* play_sound);
+
+#endif
+
diff --git a/src/protocols/rdp/bitmap.c b/src/protocols/rdp/bitmap.c
index db29316..ac4d979 100644
--- a/src/protocols/rdp/bitmap.c
+++ b/src/protocols/rdp/bitmap.c
@@ -20,6 +20,7 @@
#include "bitmap.h"
#include "common/display.h"
#include "common/surface.h"
+#include "config.h"
#include "rdp.h"
#include <cairo/cairo.h>
@@ -127,12 +128,14 @@
if (buffer != NULL)
guac_common_display_free_buffer(rdp_client->display, buffer);
- /* NOTE: FreeRDP-allocated memory for the rdpBitmap will NOT be
- * automatically released after this free handler is invoked, thus we must
- * do so manually here */
+#ifndef FREERDP_BITMAP_FREE_FREES_BITMAP
+ /* NOTE: Except in FreeRDP 2.0.0-rc0 and earlier, FreeRDP-allocated memory
+ * for the rdpBitmap will NOT be automatically released after this free
+ * handler is invoked, thus we must do so manually here */
_aligned_free(bitmap->data);
free(bitmap);
+#endif
}
diff --git a/src/protocols/rdp/client.c b/src/protocols/rdp/client.c
index db98d9b..415f226 100644
--- a/src/protocols/rdp/client.c
+++ b/src/protocols/rdp/client.c
@@ -38,12 +38,45 @@
#include <guacamole/audio.h>
#include <guacamole/client.h>
+#include <dirent.h>
#include <errno.h>
+#include <fcntl.h>
#include <pthread.h>
#include <pwd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
+#include <unistd.h>
+
+/**
+ * Tests whether the given path refers to a directory which the current user
+ * can write to. If the given path is not a directory, is not writable, or is
+ * not a link pointing to a writable directory, this test will fail, and
+ * errno will be set appropriately.
+ *
+ * @param path
+ * The path to test.
+ *
+ * @return
+ * Non-zero if the given path is (or points to) a writable directory, zero
+ * otherwise.
+ */
+static int is_writable_directory(const char* path) {
+
+ /* Verify path is writable */
+ if (faccessat(AT_FDCWD, path, W_OK, 0))
+ return 0;
+
+ /* If writable, verify path is actually a directory */
+ DIR* dir = opendir(path);
+ if (!dir)
+ return 0;
+
+ /* Path is both writable and a directory */
+ closedir(dir);
+ return 1;
+
+}
int guac_client_init(guac_client* client, int argc, char** argv) {
@@ -70,13 +103,37 @@
"assigned: %s", passwd->pw_dir, strerror(errno));
/* HOME has been successfully set */
- else
+ else {
guac_client_log(client, GUAC_LOG_DEBUG, "\"HOME\" "
"environment variable was unset and has been "
"automatically set to \"%s\"", passwd->pw_dir);
+ current_home = passwd->pw_dir;
+ }
}
+ /* Verify that detected home directory is actually writable and actually a
+ * directory, as FreeRDP initialization will mysteriously fail otherwise */
+ if (current_home != NULL && !is_writable_directory(current_home)) {
+ if (errno == EACCES)
+ guac_client_log(client, GUAC_LOG_WARNING, "FreeRDP initialization "
+ "may fail: The current user's home directory (\"%s\") is "
+ "not writable, but FreeRDP generally requires a writable "
+ "home directory for storage of configuration files and "
+ "certificates.", current_home);
+ else if (errno == ENOTDIR)
+ guac_client_log(client, GUAC_LOG_WARNING, "FreeRDP initialization "
+ "may fail: The current user's home directory (\"%s\") is "
+ "not actually a directory, but FreeRDP generally requires "
+ "a writable home directory for storage of configuration "
+ "files and certificates.", current_home);
+ else
+ guac_client_log(client, GUAC_LOG_WARNING, "FreeRDP initialization "
+ "may fail: Writability of the current user's home "
+ "directory (\"%s\") could not be determined: %s",
+ current_home, strerror(errno));
+ }
+
/* Set client args */
client->args = GUAC_RDP_CLIENT_ARGS;
diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c
index 8330c7e..d79af53 100644
--- a/src/protocols/rdp/rdp.c
+++ b/src/protocols/rdp/rdp.c
@@ -17,6 +17,7 @@
* under the License.
*/
+#include "beep.h"
#include "bitmap.h"
#include "channels/audio-input/audio-buffer.h"
#include "channels/audio-input/audio-input.h"
@@ -168,6 +169,9 @@
pointer.SetDefault = guac_rdp_pointer_set_default;
graphics_register_pointer(graphics, &pointer);
+ /* Beep on receipt of Play Sound PDU */
+ instance->update->PlaySound = guac_rdp_beep_play_sound;
+
/* Set up GDI */
instance->update->DesktopResize = guac_rdp_gdi_desktop_resize;
instance->update->EndPaint = guac_rdp_gdi_end_paint;