]> granicus.if.org Git - vim/commitdiff
patch 8.1.1502: cannot play any sound v8.1.1502
authorBram Moolenaar <Bram@vim.org>
Sun, 9 Jun 2019 11:43:51 +0000 (13:43 +0200)
committerBram Moolenaar <Bram@vim.org>
Sun, 9 Jun 2019 11:43:51 +0000 (13:43 +0200)
Problem:    Cannot play any sound.
Solution:   Use libcanberra if available.  Add sound functions.

16 files changed:
.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 [new file with mode: 0644]
src/sound.c [new file with mode: 0644]
src/testdir/Make_all.mak
src/testdir/silent.wav [new file with mode: 0644]
src/testdir/test_sound.vim [new file with mode: 0644]
src/version.c

index 51d357836bde853d5748390f3121fda06a85a3f7..53331e6ecc309144208c4c0cc13d971bf514e41c 100644 (file)
@@ -77,6 +77,7 @@ addons:
       - clang
       - lcov
       - gettext
+      - libcanberra-dev
       - libperl-dev
       - python-dev
       - python3-dev
index d6a90e2c9c68517f4d1ad41421039b60a6416d89..7bf4fd6759a4626b026b7380552f9225c24f96af 100644 (file)
--- 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 \
index 85230a60a4fd0a3d152a1a05d5d8a318023ccbe5..d1f6824b6d939569c03c03279614aca42a2193d1 100644 (file)
@@ -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'
index 65398d01841495650b8b84954e66ed5b0138389a..eef91ece2fc964d44cf80cfe047570937923acbe 100644 (file)
@@ -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 \
index 1f5ee2addbebcd20d70703082d46e4e504efd3de..d5b4f6053ef70da651285d8da6c8433e0b9a8b68 100755 (executable)
@@ -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
index 23e301c90e3c372543dc2feaf748439c06327ce8..c1ced6fe9c533e678c8479cacfef0dfed319c371 100644 (file)
 #undef HAVE_STRNICMP
 #undef HAVE_STRPBRK
 #undef HAVE_STRTOL
+#undef HAVE_CANBERRA
 #undef HAVE_ST_BLKSIZE
 #undef HAVE_SYSCONF
 #undef HAVE_SYSCTL
index 773844a0d8dc909226bb87494e4c1222f82ab754..7e821e63b5d7fb1619eede3681bb28b3a4f203d0 100644 (file)
@@ -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(
index bc6056785954582264a24e4d050da9e859362abb..42973797de0166ab555bf43c39f4caf7593c221c 100644 (file)
@@ -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
index c613c4bc94bc6be0c86490b0e343fc7ca354b022..c5d7d777e66cfe6b538d9ac73c46b97b43356056 100644 (file)
 # 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)
index e606f08f5dc636785f8b2f9c02985c907658b4f5..264cbb9e453ef96736fafd07af48aeec4382cb8e 100644 (file)
@@ -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 (file)
index 0000000..43e4727
--- /dev/null
@@ -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 (file)
index 0000000..ac6c377
--- /dev/null
@@ -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
index 34644d6b9518793005de3aa2d80d5ae03982b9f8..08f6c5bca606536de7e481d494dd610a6d727d44 100644 (file)
@@ -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 (file)
index 0000000..4631a7e
Binary files /dev/null and b/src/testdir/silent.wav differ
diff --git a/src/testdir/test_sound.vim b/src/testdir/test_sound.vim
new file mode 100644 (file)
index 0000000..54c6a29
--- /dev/null
@@ -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
index 3dcf0c6314416fd3b7de118055ff7d3a73787022..5a69befa7bdfafbada4a00406b52ecac8c4c764c 100644 (file)
@@ -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
@@ -767,6 +777,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1502,
 /**/
     1501,
 /**/