summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml1
-rw-r--r--Filelist3
-rw-r--r--runtime/doc/eval.txt50
-rw-r--r--src/Makefile10
-rwxr-xr-xsrc/auto/configure195
-rw-r--r--src/config.h.in1
-rw-r--r--src/configure.ac35
-rw-r--r--src/evalfunc.c9
-rw-r--r--src/feature.h7
-rw-r--r--src/proto.h1
-rw-r--r--src/proto/sound.pro7
-rw-r--r--src/sound.c193
-rw-r--r--src/testdir/Make_all.mak2
-rw-r--r--src/testdir/silent.wavbin0 -> 65580 bytes
-rw-r--r--src/testdir/test_sound.vim45
-rw-r--r--src/version.c12
16 files changed, 440 insertions, 131 deletions
diff --git a/.travis.yml b/.travis.yml
index 51d357836..53331e6ec 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -77,6 +77,7 @@ addons:
- clang
- lcov
- gettext
+ - libcanberra-dev
- libperl-dev
- python-dev
- python3-dev
diff --git a/Filelist b/Filelist
index d6a90e2c9..7bf4fd675 100644
--- a/Filelist
+++ b/Filelist
@@ -88,6 +88,7 @@ SRC_ALL = \
src/search.c \
src/sha256.c \
src/sign.c \
+ src/sound.c \
src/spell.c \
src/spell.h \
src/spellfile.c \
@@ -150,6 +151,7 @@ SRC_ALL = \
src/testdir/samples/test000 \
src/testdir/if_ver*.vim \
src/testdir/color_ramp.vim \
+ src/testdir/silent.wav \
src/proto.h \
src/protodef.h \
src/proto/arabic.pro \
@@ -209,6 +211,7 @@ SRC_ALL = \
src/proto/search.pro \
src/proto/sha256.pro \
src/proto/sign.pro \
+ src/proto/sound.pro \
src/proto/spell.pro \
src/proto/spellfile.pro \
src/proto/syntax.pro \
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 85230a60a..d1f6824b6 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -2622,6 +2622,12 @@ sin({expr}) Float sine of {expr}
sinh({expr}) Float hyperbolic sine of {expr}
sort({list} [, {func} [, {dict}]])
List sort {list}, using {func} to compare
+sound_playevent({name} [, {callback}])
+ Number play an event sound
+sound_playfile({name} [, {callback}])
+ Number play a sound file
+sound_stop({id}) none stop playing sound {id}
+sound_stopall() none stop playing all sounds
soundfold({word}) String sound-fold {word}
spellbadword() String badly spelled word at cursor
spellsuggest({word} [, {max} [, {capital}]])
@@ -8837,6 +8843,49 @@ sort({list} [, {func} [, {dict}]]) *sort()* *E702*
return a:i1 - a:i2
endfunc
<
+ *sound_playevent()*
+sound_playevent({name} [, {callback}])
+ Play a sound identified by {name}. Which event names are
+ supported depends on the system. Often the XDG sound names
+ are used. On Ubuntu they may be found in
+ /usr/share/sounds/freedesktop/stereo. Example: >
+ call sound_playevent('bell')
+
+< When {callback} is specified it is invoked when the sound is
+ finished. The first argument is the sound ID, the second
+ argument is the status:
+ 0 sound was played to the end
+ 1 sound was interruped
+ 2 error occured after sound started
+ Example: >
+ func Callback(id, status)
+ echomsg "sound " .. a:id .. " finished with " .. a:status
+ endfunc
+ call sound_playevent('bell', 'Callback')
+
+< Returns the sound ID, which can be passed to `sound_stop()`.
+ Returns zero if the sound could not be played.
+ {only available when compiled with the +sound feature}
+
+ *sound_playfile()*
+sound_playfile({name} [, {callback}])
+ Like `sound_playevent()` but play sound file {name}. {name}
+ must be a full path. On Ubuntu you may find files to play
+ with this command: >
+ :!find /usr/share/sounds -type f | grep -v index.theme
+
+< {only available when compiled with the +sound feature}
+
+
+sound_stop({id}) *sound_stop()*
+ Stop playing sound {id}. {id} must be previously returned by
+ `sound_playevent()` or `sound_playfile()`.
+ {only available when compiled with the +sound feature}
+
+sound_stopall() *sound_stopall()*
+ Stop playing all sounds.
+ {only available when compiled with the +sound feature}
+
*soundfold()*
soundfold({word})
Return the sound-folded equivalent of {word}. Uses the first
@@ -10756,6 +10805,7 @@ scrollbind Compiled with 'scrollbind' support. (always true)
showcmd Compiled with 'showcmd' support.
signs Compiled with |:sign| support.
smartindent Compiled with 'smartindent' support.
+sound Compiled with sound support, e.g. `sound_playevent()`
spell Compiled with spell checking support |spell|.
startuptime Compiled with |--startuptime| support.
statusline Compiled with support for 'statusline', 'rulerformat'
diff --git a/src/Makefile b/src/Makefile
index 65398d018..eef91ece2 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -1628,6 +1628,7 @@ BASIC_SRC = \
search.c \
sha256.c \
sign.c \
+ sound.c \
spell.c \
spellfile.c \
syntax.c \
@@ -1743,6 +1744,7 @@ OBJ_COMMON = \
objects/search.o \
objects/sha256.o \
objects/sign.o \
+ objects/sound.o \
objects/spell.o \
objects/spellfile.o \
objects/syntax.o \
@@ -1883,6 +1885,7 @@ PRO_AUTO = \
search.pro \
sha256.pro \
sign.pro \
+ sound.pro \
spell.pro \
spellfile.pro \
syntax.pro \
@@ -3235,6 +3238,9 @@ objects/sha256.o: sha256.c
objects/sign.o: sign.c
$(CCC) -o $@ sign.c
+objects/sound.o: sound.c
+ $(CCC) -o $@ sound.c
+
objects/spell.o: spell.c
$(CCC) -o $@ spell.c
@@ -3650,6 +3656,10 @@ objects/sign.o: sign.c vim.h protodef.h auto/config.h feature.h os_unix.h \
auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
proto.h globals.h
+objects/sound.o: spell.c vim.h protodef.h auto/config.h feature.h os_unix.h \
+ auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
+ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
+ proto.h globals.h
objects/spell.o: spell.c vim.h protodef.h auto/config.h feature.h os_unix.h \
auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
diff --git a/src/auto/configure b/src/auto/configure
index 1f5ee2add..d5b4f6053 100755
--- a/src/auto/configure
+++ b/src/auto/configure
@@ -9303,28 +9303,8 @@ fi
-
-if test -z "$SKIP_GTK2"; then
-
- { $as_echo "$as_me:${as_lineno-$LINENO}: checking --disable-gtktest argument" >&5
-$as_echo_n "checking --disable-gtktest argument... " >&6; }
- # Check whether --enable-gtktest was given.
-if test "${enable_gtktest+set}" = set; then :
- enableval=$enable_gtktest;
-else
- enable_gtktest=yes
-fi
-
- if test "x$enable_gtktest" = "xyes" ; then
- { $as_echo "$as_me:${as_lineno-$LINENO}: result: gtk test enabled" >&5
-$as_echo "gtk test enabled" >&6; }
- else
- { $as_echo "$as_me:${as_lineno-$LINENO}: result: gtk test disabled" >&5
-$as_echo "gtk test disabled" >&6; }
- fi
-
- if test "X$PKG_CONFIG" = "X"; then
- if test -n "$ac_tool_prefix"; then
+if test "X$PKG_CONFIG" = "X"; then
+ if test -n "$ac_tool_prefix"; then
# Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args.
set dummy ${ac_tool_prefix}pkg-config; ac_word=$2
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
@@ -9422,6 +9402,26 @@ else
PKG_CONFIG="$ac_cv_path_PKG_CONFIG"
fi
+fi
+
+
+if test -z "$SKIP_GTK2"; then
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking --disable-gtktest argument" >&5
+$as_echo_n "checking --disable-gtktest argument... " >&6; }
+ # Check whether --enable-gtktest was given.
+if test "${enable_gtktest+set}" = set; then :
+ enableval=$enable_gtktest;
+else
+ enable_gtktest=yes
+fi
+
+ if test "x$enable_gtktest" = "xyes" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: gtk test enabled" >&5
+$as_echo "gtk test enabled" >&6; }
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: gtk test disabled" >&5
+$as_echo "gtk test disabled" >&6; }
fi
if test "x$PKG_CONFIG" != "xno"; then
@@ -9677,107 +9677,6 @@ $as_echo "gtk test enabled" >&6; }
$as_echo "gtk test disabled" >&6; }
fi
- if test "X$PKG_CONFIG" = "X"; then
- if test -n "$ac_tool_prefix"; then
- # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args.
-set dummy ${ac_tool_prefix}pkg-config; ac_word=$2
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
-$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_path_PKG_CONFIG+:} false; then :
- $as_echo_n "(cached) " >&6
-else
- case $PKG_CONFIG in
- [\\/]* | ?:[\\/]*)
- ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path.
- ;;
- *)
- as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
-for as_dir in $PATH
-do
- IFS=$as_save_IFS
- test -z "$as_dir" && as_dir=.
- for ac_exec_ext in '' $ac_executable_extensions; do
- if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
- ac_cv_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext"
- $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
- break 2
- fi
-done
- done
-IFS=$as_save_IFS
-
- ;;
-esac
-fi
-PKG_CONFIG=$ac_cv_path_PKG_CONFIG
-if test -n "$PKG_CONFIG"; then
- { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5
-$as_echo "$PKG_CONFIG" >&6; }
-else
- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-fi
-
-
-fi
-if test -z "$ac_cv_path_PKG_CONFIG"; then
- ac_pt_PKG_CONFIG=$PKG_CONFIG
- # Extract the first word of "pkg-config", so it can be a program name with args.
-set dummy pkg-config; ac_word=$2
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
-$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_path_ac_pt_PKG_CONFIG+:} false; then :
- $as_echo_n "(cached) " >&6
-else
- case $ac_pt_PKG_CONFIG in
- [\\/]* | ?:[\\/]*)
- ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path.
- ;;
- *)
- as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
-for as_dir in $PATH
-do
- IFS=$as_save_IFS
- test -z "$as_dir" && as_dir=.
- for ac_exec_ext in '' $ac_executable_extensions; do
- if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
- ac_cv_path_ac_pt_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext"
- $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
- break 2
- fi
-done
- done
-IFS=$as_save_IFS
-
- ;;
-esac
-fi
-ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG
-if test -n "$ac_pt_PKG_CONFIG"; then
- { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_pt_PKG_CONFIG" >&5
-$as_echo "$ac_pt_PKG_CONFIG" >&6; }
-else
- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-fi
-
- if test "x$ac_pt_PKG_CONFIG" = x; then
- PKG_CONFIG="no"
- else
- case $cross_compiling:$ac_tool_warned in
-yes:)
-{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
-$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
-ac_tool_warned=yes ;;
-esac
- PKG_CONFIG=$ac_pt_PKG_CONFIG
- fi
-else
- PKG_CONFIG="$ac_cv_path_PKG_CONFIG"
-fi
-
- fi
-
if test "x$PKG_CONFIG" != "xno"; then
if test "X$GTK_CONFIG" != "Xno" -o "X$PKG_CONFIG" != "Xno"; then
@@ -13026,6 +12925,56 @@ rm -rf conftest*
fi
+
+if test "x$PKG_CONFIG" != "xno"; then
+ canberra_lib=`$PKG_CONFIG --libs libcanberrax 2>/dev/null`
+ canberra_cflags=`$PKG_CONFIG --cflags libcanberrax 2>/dev/null`
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: canberra_lib: $canberra_lib" >&5
+$as_echo "canberra_lib: $canberra_lib" >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: canberra_cflags: $canberra_cflags" >&5
+$as_echo "canberra_cflags: $canberra_cflags" >&6; }
+fi
+if test "x$canberra_lib" = "x"; then
+ canberra_lib=-lcanberra
+ canberra_cflags=-D_REENTRANT
+fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: canberra_lib: $canberra_lib" >&5
+$as_echo "canberra_lib: $canberra_lib" >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: canberra_cflags: $canberra_cflags" >&5
+$as_echo "canberra_cflags: $canberra_cflags" >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for libcanberra" >&5
+$as_echo_n "checking for libcanberra... " >&6; }
+ac_save_CFLAGS="$CFLAGS"
+ac_save_LIBS="$LIBS"
+CFLAGS="$CFLAGS $canberra_cflags"
+LIBS="$LIBS $canberra_lib"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+# include <canberra.h>
+
+int
+main ()
+{
+
+ ca_context *hello;
+ ca_context_create(&hello);
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }; $as_echo "#define HAVE_CANBERRA 1" >>confdefs.h
+
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }; CFLAGS="$ac_save_CFLAGS"; LIBS="$ac_save_LIBS"
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for st_blksize" >&5
$as_echo_n "checking for st_blksize... " >&6; }
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
diff --git a/src/config.h.in b/src/config.h.in
index 23e301c90..c1ced6fe9 100644
--- a/src/config.h.in
+++ b/src/config.h.in
@@ -207,6 +207,7 @@
#undef HAVE_STRNICMP
#undef HAVE_STRPBRK
#undef HAVE_STRTOL
+#undef HAVE_CANBERRA
#undef HAVE_ST_BLKSIZE
#undef HAVE_SYSCONF
#undef HAVE_SYSCTL
diff --git a/src/configure.ac b/src/configure.ac
index 773844a0d..7e821e63b 100644
--- a/src/configure.ac
+++ b/src/configure.ac
@@ -2702,6 +2702,10 @@ AC_DEFUN([GNOME_INIT],[
GNOME_INIT_HOOK([],fail)
])
+if test "X$PKG_CONFIG" = "X"; then
+ AC_PATH_TOOL(PKG_CONFIG, pkg-config, no)
+fi
+
dnl ---------------------------------------------------------------------------
dnl Check for GTK2. If it fails, then continue on for Motif as before...
@@ -2717,10 +2721,6 @@ if test -z "$SKIP_GTK2"; then
AC_MSG_RESULT(gtk test disabled)
fi
- if test "X$PKG_CONFIG" = "X"; then
- AC_PATH_TOOL(PKG_CONFIG, pkg-config, no)
- fi
-
if test "x$PKG_CONFIG" != "xno"; then
dnl First try finding version 2.2.0 or later. The 2.0.x series has
dnl problems (bold fonts, --remote doesn't work).
@@ -2769,10 +2769,6 @@ if test -z "$SKIP_GTK3"; then
AC_MSG_RESULT(gtk test disabled)
fi
- if test "X$PKG_CONFIG" = "X"; then
- AC_PATH_TOOL(PKG_CONFIG, pkg-config, no)
- fi
-
if test "x$PKG_CONFIG" != "xno"; then
AM_PATH_GTK(3.0.0,
[GUI_LIB_LOC="$GTK_LIBDIR"
@@ -3755,6 +3751,29 @@ dnl define _LARGE_FILES, _FILE_OFFSET_BITS and _LARGEFILE_SOURCE when
dnl appropriate, so that off_t is 64 bits when needed.
AC_SYS_LARGEFILE
+
+if test "x$PKG_CONFIG" != "xno"; then
+ canberra_lib=`$PKG_CONFIG --libs libcanberra 2>/dev/null`
+ canberra_cflags=`$PKG_CONFIG --cflags libcanberra 2>/dev/null`
+fi
+if test "x$canberra_lib" = "x"; then
+ canberra_lib=-lcanberra
+ canberra_cflags=-D_REENTRANT
+fi
+AC_MSG_CHECKING(for libcanberra)
+ac_save_CFLAGS="$CFLAGS"
+ac_save_LIBS="$LIBS"
+CFLAGS="$CFLAGS $canberra_cflags"
+LIBS="$LIBS $canberra_lib"
+AC_TRY_LINK([
+# include <canberra.h>
+ ], [
+ ca_context *hello;
+ ca_context_create(&hello);],
+ AC_MSG_RESULT(yes); AC_DEFINE(HAVE_CANBERRA),
+ AC_MSG_RESULT(no); CFLAGS="$ac_save_CFLAGS"; LIBS="$ac_save_LIBS")
+
+
dnl fstatfs() can take 2 to 4 arguments, try to use st_blksize if possible
AC_MSG_CHECKING(for st_blksize)
AC_TRY_COMPILE(
diff --git a/src/evalfunc.c b/src/evalfunc.c
index bc6056785..42973797d 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -925,6 +925,12 @@ static struct fst
{"sinh", 1, 1, f_sinh},
#endif
{"sort", 1, 3, f_sort},
+#ifdef FEAT_SOUND
+ {"sound_playevent", 1, 2, f_sound_playevent},
+ {"sound_playfile", 1, 2, f_sound_playfile},
+ {"sound_stop", 1, 1, f_sound_stop},
+ {"sound_stopall", 0, 0, f_sound_stopall},
+#endif
{"soundfold", 1, 1, f_soundfold},
{"spellbadword", 0, 1, f_spellbadword},
{"spellsuggest", 1, 3, f_spellsuggest},
@@ -6782,6 +6788,9 @@ f_has(typval_T *argvars, typval_T *rettv)
#ifdef FEAT_NETBEANS_INTG
"netbeans_intg",
#endif
+#ifdef FEAT_SOUND
+ "sound",
+#endif
#ifdef FEAT_SPELL
"spell",
#endif
diff --git a/src/feature.h b/src/feature.h
index c613c4bc9..c5d7d777e 100644
--- a/src/feature.h
+++ b/src/feature.h
@@ -660,6 +660,13 @@
# define FEAT_TERM_POPUP_MENU
#endif
+/*
+ * sound - currently only with libcanberra
+ */
+#if !defined(FEAT_SOUND) && defined(FEAT_BIG) && defined(HAVE_CANBERRA)
+# define FEAT_SOUND
+#endif
+
/* There are two ways to use XPM. */
#if (defined(HAVE_XM_XPMP_H) && defined(FEAT_GUI_MOTIF)) \
|| defined(HAVE_X11_XPM_H)
diff --git a/src/proto.h b/src/proto.h
index e606f08f5..264cbb9e4 100644
--- a/src/proto.h
+++ b/src/proto.h
@@ -183,6 +183,7 @@ void qsort(void *base, size_t elm_count, size_t elm_size, int (*cmp)(const void
# ifdef FEAT_SIGNS
# include "sign.pro"
# endif
+# include "sound.pro"
# include "spell.pro"
# include "spellfile.pro"
# include "syntax.pro"
diff --git a/src/proto/sound.pro b/src/proto/sound.pro
new file mode 100644
index 000000000..43e472775
--- /dev/null
+++ b/src/proto/sound.pro
@@ -0,0 +1,7 @@
+/* sound.c */
+void f_sound_playevent(typval_T *argvars, typval_T *rettv);
+void f_sound_playfile(typval_T *argvars, typval_T *rettv);
+void f_sound_stop(typval_T *argvars, typval_T *rettv);
+void f_sound_stopall(typval_T *argvars, typval_T *rettv);
+void sound_free(void);
+/* vim: set ft=c : */
diff --git a/src/sound.c b/src/sound.c
new file mode 100644
index 000000000..ac6c377be
--- /dev/null
+++ b/src/sound.c
@@ -0,0 +1,193 @@
+/* vi:set ts=8 sts=4 sw=4 noet:
+ *
+ * VIM - Vi IMproved by Bram Moolenaar
+ *
+ * Do ":help uganda" in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+/*
+ * sound.c: functions related making noise
+ */
+
+#include "vim.h"
+
+#if (defined(FEAT_SOUND) && defined(HAVE_CANBERRA)) || defined(PROTO)
+
+#include <canberra.h>
+
+static long sound_id = 0;
+static ca_context *context = NULL;
+
+typedef struct soundcb_S soundcb_T;
+
+struct soundcb_S {
+ callback_T snd_callback;
+ soundcb_T *snd_next;
+};
+
+static soundcb_T *first_callback = NULL;
+
+ static soundcb_T *
+get_sound_callback(typval_T *arg)
+{
+ callback_T callback;
+ soundcb_T *soundcb;
+
+ if (arg->v_type == VAR_UNKNOWN)
+ return NULL;
+ callback = get_callback(arg);
+ if (callback.cb_name == NULL)
+ return NULL;
+
+ soundcb = ALLOC_ONE(soundcb_T);
+ if (soundcb == NULL)
+ free_callback(&callback);
+ else
+ {
+ soundcb->snd_next = first_callback;
+ first_callback = soundcb;
+ set_callback(&soundcb->snd_callback, &callback);
+ }
+ return soundcb;
+}
+
+/*
+ * Delete "soundcb" from the list of pending callbacks.
+ */
+ static void
+delete_sound_callback(soundcb_T *soundcb)
+{
+ soundcb_T *p;
+ soundcb_T *prev = NULL;
+
+ for (p = first_callback; p != NULL; prev = p, p = p->snd_next)
+ if (p == soundcb)
+ {
+ if (prev == NULL)
+ first_callback = p->snd_next;
+ else
+ prev->snd_next = p->snd_next;
+ free_callback(&p->snd_callback);
+ vim_free(p);
+ break;
+ }
+}
+
+ static void
+sound_callback(
+ ca_context *c UNUSED,
+ uint32_t id,
+ int error_code,
+ void *userdata)
+{
+ soundcb_T *soundcb = (soundcb_T *)userdata;
+ typval_T argv[3];
+ typval_T rettv;
+ int dummy;
+
+ argv[0].v_type = VAR_NUMBER;
+ argv[0].vval.v_number = id;
+ argv[1].v_type = VAR_NUMBER;
+ argv[1].vval.v_number = error_code == CA_SUCCESS ? 0
+ : error_code == CA_ERROR_CANCELED
+ || error_code == CA_ERROR_DESTROYED
+ ? 1 : 2;
+ argv[2].v_type = VAR_UNKNOWN;
+
+ call_callback(&soundcb->snd_callback, -1,
+ &rettv, 2, argv, NULL, 0L, 0L, &dummy, TRUE, NULL);
+ clear_tv(&rettv);
+
+ delete_sound_callback(soundcb);
+ redraw_after_callback(TRUE);
+}
+
+ static void
+sound_play_common(typval_T *argvars, typval_T *rettv, int playfile)
+{
+ if (context == NULL)
+ ca_context_create(&context);
+ if (context != NULL)
+ {
+ soundcb_T *soundcb = get_sound_callback(&argvars[1]);
+ int res = CA_ERROR_INVALID;
+
+ ++sound_id;
+ if (soundcb == NULL)
+ {
+ res = ca_context_play(context, sound_id,
+ playfile ? CA_PROP_MEDIA_FILENAME : CA_PROP_EVENT_ID,
+ tv_get_string(&argvars[0]),
+ CA_PROP_CANBERRA_CACHE_CONTROL, "volatile",
+ NULL);
+ }
+ else
+ {
+ static ca_proplist *proplist = NULL;
+
+ ca_proplist_create(&proplist);
+ if (proplist != NULL)
+ {
+ if (playfile)
+ ca_proplist_sets(proplist, CA_PROP_MEDIA_FILENAME,
+ (char *)tv_get_string(&argvars[0]));
+ else
+ ca_proplist_sets(proplist, CA_PROP_EVENT_ID,
+ (char *)tv_get_string(&argvars[0]));
+ ca_proplist_sets(proplist, CA_PROP_CANBERRA_CACHE_CONTROL,
+ "volatile");
+ res = ca_context_play_full(context, sound_id, proplist,
+ sound_callback, soundcb);
+ if (res != CA_SUCCESS)
+ delete_sound_callback(soundcb);
+
+ ca_proplist_destroy(proplist);
+ }
+ }
+ rettv->vval.v_number = res == CA_SUCCESS ? sound_id : 0;
+ }
+}
+
+ void
+f_sound_playevent(typval_T *argvars, typval_T *rettv)
+{
+ sound_play_common(argvars, rettv, FALSE);
+}
+
+ void
+f_sound_playfile(typval_T *argvars, typval_T *rettv)
+{
+ sound_play_common(argvars, rettv, TRUE);
+}
+
+ void
+f_sound_stop(typval_T *argvars, typval_T *rettv UNUSED)
+{
+ if (context != NULL)
+ ca_context_cancel(context, tv_get_number(&argvars[0]));
+}
+
+ void
+f_sound_stopall(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
+{
+ if (context != NULL)
+ {
+ ca_context_destroy(context);
+ context = NULL;
+ }
+}
+
+#if defined(EXITFREE) || defined(PROTO)
+ void
+sound_free(void)
+{
+ if (context != NULL)
+ ca_context_destroy(context);
+ while (first_callback != NULL)
+ delete_sound_callback(first_callback);
+}
+#endif
+
+#endif // FEAT_SOUND && HAVE_CANBERRA
diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak
index 34644d6b9..08f6c5bca 100644
--- a/src/testdir/Make_all.mak
+++ b/src/testdir/Make_all.mak
@@ -228,6 +228,7 @@ NEW_TESTS = \
test_signs \
test_smartindent \
test_sort \
+ test_sound \
test_source \
test_source_utf8 \
test_spell \
@@ -399,6 +400,7 @@ NEW_TESTS_RES = \
test_signals.res \
test_signs.res \
test_smartindent.res \
+ test_sound.res \
test_source.res \
test_spell.res \
test_startup.res \
diff --git a/src/testdir/silent.wav b/src/testdir/silent.wav
new file mode 100644
index 000000000..4631a7e8e
--- /dev/null
+++ b/src/testdir/silent.wav
Binary files differ
diff --git a/src/testdir/test_sound.vim b/src/testdir/test_sound.vim
new file mode 100644
index 000000000..54c6a29db
--- /dev/null
+++ b/src/testdir/test_sound.vim
@@ -0,0 +1,45 @@
+" Tests for the sound feature
+
+if !has('sound')
+ throw 'Skipped: sound feature not available'
+endif
+
+func PlayCallback(id, result)
+ let g:id = a:id
+ let g:result = a:result
+endfunc
+
+func Test_play_event()
+ let id = sound_playevent('bell', 'PlayCallback')
+ if id == 0
+ throw 'Skipped: bell event not available'
+ endif
+ " Stop it quickly, avoid annoying the user.
+ sleep 20m
+ call sound_stop(id)
+ sleep 20m
+ call assert_equal(id, g:id)
+ call assert_equal(1, g:result) " sound was aborted
+endfunc
+
+func Test_play_silent()
+ let fname = fnamemodify('silent.wav', '%p')
+
+ " play without callback
+ let id1 = sound_playfile(fname)
+ call assert_true(id1 > 0)
+
+ " play until the end
+ let id2 = sound_playfile(fname, 'PlayCallback')
+ call assert_true(id2 > 0)
+ sleep 500m
+ call assert_equal(id2, g:id)
+ call assert_equal(0, g:result)
+
+ let id2 = sound_playfile(fname, 'PlayCallback')
+ call assert_true(id2 > 0)
+ sleep 20m
+ call sound_stopall()
+ call assert_equal(id2, g:id)
+ call assert_equal(1, g:result)
+endfunc
diff --git a/src/version.c b/src/version.c
index 3dcf0c631..5a69befa7 100644
--- a/src/version.c
+++ b/src/version.c
@@ -580,6 +580,16 @@ static char *(features[]) =
#else
"-smartindent",
#endif
+#ifdef FEAT_SOUND
+ "+sound",
+#else
+ "-sound",
+#endif
+#ifdef FEAT_SPELL
+ "+spell",
+#else
+ "-spell",
+#endif
#ifdef STARTUPTIME
"+startuptime",
#else
@@ -768,6 +778,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 1502,
+/**/
1501,
/**/
1500,