changeset 17004:353ed7ef78df v8.1.1502

patch 8.1.1502: cannot play any sound commit https://github.com/vim/vim/commit/427f5b66ce0abe19daed9291b1693f6e8aae6552 Author: Bram Moolenaar <Bram@vim.org> Date: Sun Jun 9 13:43:51 2019 +0200 patch 8.1.1502: cannot play any sound Problem: Cannot play any sound. Solution: Use libcanberra if available. Add sound functions.
author Bram Moolenaar <Bram@vim.org>
date Sun, 09 Jun 2019 13:45:06 +0200
parents 0363f6e9eac3
children bf7a8a466977
files .travis.yml Filelist runtime/doc/eval.txt src/Makefile src/auto/configure src/config.h.in src/configure.ac src/evalfunc.c src/feature.h src/proto.h src/proto/sound.pro src/sound.c src/testdir/Make_all.mak src/testdir/silent.wav src/testdir/test_sound.vim src/version.c
diffstat 16 files changed, 440 insertions(+), 131 deletions(-) [+]
line wrap: on
line diff
--- a/.travis.yml
+++ b/.travis.yml
@@ -77,6 +77,7 @@ addons:
       - clang
       - lcov
       - gettext
+      - libcanberra-dev
       - libperl-dev
       - python-dev
       - python3-dev
--- 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 \
--- 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}]])			*so
 			   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' s
 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'
--- 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/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 \
--- 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 @@ fi
 $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
--- 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
--- 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_BI
 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(
--- 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
--- 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)
--- a/src/proto.h
+++ b/src/proto.h
@@ -183,6 +183,7 @@ void qsort(void *base, size_t elm_count,
 # ifdef FEAT_SIGNS
 #  include "sign.pro"
 # endif
+# include "sound.pro"
 # include "spell.pro"
 # include "spellfile.pro"
 # include "syntax.pro"
new file mode 100644
--- /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 : */
new file mode 100644
--- /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
--- 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 \
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..4631a7e8ed007b62af0a63111b9e4adc60a9eebe
GIT binary patch
literal 65580
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
E0p>-wfB*mh
new file mode 100644
--- /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
--- 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,