# HG changeset patch # User Bram Moolenaar # Date 1560803406 -7200 # Node ID be5a5cfc991a343be177fc7e6a8251600a773ff2 # Parent 808ed7403f4fbe88d341b9ea3168d9f446393d82 patch 8.1.1565: MS-Windows: no sound support commit https://github.com/vim/vim/commit/9b283523f2f75b45feef902b8713808e883d9c19 Author: Bram Moolenaar Date: Mon Jun 17 22:19:33 2019 +0200 patch 8.1.1565: MS-Windows: no sound support Problem: MS-Windows: no sound support. Solution: Add sound support for MS-Windows. (Yasuhiro Matsumoto, Ken Takata, closes #4522) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1,4 +1,4 @@ -*eval.txt* For Vim version 8.1. Last change: 2019 Jun 10 +*eval.txt* For Vim version 8.1. Last change: 2019 Jun 17 VIM REFERENCE MANUAL by Bram Moolenaar @@ -2183,7 +2183,7 @@ v:val Value of the current item of a |L *v:version* *version-variable* v:version Version number of Vim: Major version number times 100 plus - minor version number. Version 5.0 is 500. Version 5.1 (5.01) + minor version number. Version 5.0 is 500. Version 5.1 is 501. Read-only. "version" also works, for backwards compatibility, unless |scriptversion| is 3 or higher. Use |has()| to check if a certain patch was included, e.g.: > @@ -2193,10 +2193,10 @@ v:version Version number of Vim: Major v completely different. *v:versionlong* *versionlong-variable* -v:versionlong Like v:version, but also including the patchlevel. Version - 8.1 with patch 1234 has value 8011234. This can be used like - this: > - if v:versionlong >= 8011234 +v:versionlong Like v:version, but also including the patchlevel in the last + four digits. Version 8.1 with patch 123 has value 8010123. + This can be used like this: > + if v:versionlong >= 8010123 < However, if there are gaps in the list of patches included this will not work well. This can happen if a recent patch was included into an older version, e.g. for a security fix. @@ -8123,10 +8123,9 @@ setbufline({expr}, {lnum}, {text}) *se {lnum} is used like with |setline()|. This works like |setline()| for the specified buffer. - On success 0 is returned, on failure 1 is returned. - - If {expr} is not a valid buffer or {lnum} is not valid, an - error message is given. + + When {expr} is not a valid buffer or {lnum} is not valid then + 1 is returned. On success 0 is returned. setbufvar({expr}, {varname}, {val}) *setbufvar()* Set option or local variable {varname} in buffer {expr} to @@ -8884,7 +8883,7 @@ sort({list} [, {func} [, {dict}]]) *so < sound_clear() *sound_clear()* Stop playing all sounds. - {only available when compiled with the +sound feature} + {only available when compiled with the |+sound| feature} *sound_playevent()* sound_playevent({name} [, {callback}]) @@ -8893,8 +8892,11 @@ sound_playevent({name} [, {callback}]) 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 +< On MS-Windows, {name} can be SystemAsterisk, SystemDefault, + SystemExclamation, SystemExit, SystemHand, SystemQuestion, + SystemStart, SystemWelcome, etc. + + 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 @@ -8906,7 +8908,9 @@ sound_playevent({name} [, {callback}]) endfunc call sound_playevent('bell', 'Callback') -< Returns the sound ID, which can be passed to `sound_stop()`. +< MS-Windows: {callback} doesn't work for this function. + + 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} @@ -8922,6 +8926,10 @@ sound_playfile({path} [, {callback}]) sound_stop({id}) *sound_stop()* Stop playing sound {id}. {id} must be previously returned by `sound_playevent()` or `sound_playfile()`. + + On MS-Windows, this does not work for event sound started by + `sound_playevent()`. To stop event sounds, use `sound_clear()`. + {only available when compiled with the |+sound| feature} *soundfold()* @@ -11592,7 +11600,6 @@ text... # Number * Funcref - :unl[et][!] {name} ... *:unlet* *:unl* *E108* *E795* Remove the internal variable {name}. Several variable names can be given, they are all removed. The name @@ -11637,7 +11644,7 @@ text... < This is useful if you want to make sure the variable is not modified. *E995* - |:const| does not allow to for changing a variable. > + |:const| does not allow to for changing a variable: > :let x = 1 :const x = 2 " Error! < *E996* diff --git a/src/Make_cyg_ming.mak b/src/Make_cyg_ming.mak --- a/src/Make_cyg_ming.mak +++ b/src/Make_cyg_ming.mak @@ -106,6 +106,13 @@ else TERMINAL=no endif +# Set to yes to enable sound support. +ifneq ($(findstring $(FEATURES),BIG HUGE),) +SOUND=yes +else +SOUND=no +endif + ifndef CTAGS # this assumes ctags is Exuberant ctags CTAGS = ctags -I INIT+ --fields=+S @@ -633,6 +640,10 @@ TERM_DEPS = \ libvterm/src/vterm_internal.h endif +ifeq ($(SOUND),yes) +DEFINES += -DFEAT_SOUND +endif + # DirectWrite (DirectX) ifeq ($(DIRECTX),yes) # Only allow DirectWrite for a GUI build. @@ -849,6 +860,10 @@ OBJ += $(OUTDIR)/terminal.o \ $(OUTDIR)/vterm.o endif +ifeq ($(SOUND),yes) +OBJ += $(OUTDIR)/sound.o +endif + # Include xdiff OBJ += $(OUTDIR)/xdiffi.o \ $(OUTDIR)/xemit.o \ @@ -957,6 +972,10 @@ CFLAGS += -I$(ICONV) DEFINES+=-DDYNAMIC_ICONV endif +ifeq (yes, $(SOUND)) +LIB += -lwinmm +endif + ifeq (yes, $(USE_STDCPLUS)) LINK = $(CXX) ifeq (yes, $(STATIC_STDCPLUS)) diff --git a/src/Make_mvc.mak b/src/Make_mvc.mak --- a/src/Make_mvc.mak +++ b/src/Make_mvc.mak @@ -38,7 +38,9 @@ # is yes) # Global IME support: GIME=yes (requires GUI=yes) # -# Terminal support: TERMINAL=yes (default is yes) +# Terminal support: TERMINAL=yes (default is yes) +# +# Sound support: SOUND=yes (default is yes) # # DLL support (EXPERIMENTAL): VIMDLL=yes (default is no) # Creates vim{32,64}.dll, and stub gvim.exe and vim.exe. @@ -381,6 +383,14 @@ TERM_DEPS = \ libvterm/src/vterm_internal.h !endif +!ifndef SOUND +! if "$(FEATURES)"=="HUGE" || "$(FEATURES)"=="BIG" +SOUND = yes +! else +SOUND = no +! endif +!endif + !ifndef NETBEANS NETBEANS = $(GUI) !endif @@ -454,6 +464,13 @@ XPM_INC = -I $(XPM)\include -I $(XPM)\ ! endif !endif # GUI +!if "$(SOUND)" == "yes" +SOUND_PRO = proto/sound.pro +SOUND_OBJ = $(OBJDIR)/sound.obj +SOUND_DEFS = -DFEAT_SOUND +SOUND_LIB = winmm.lib +!endif + !if "$(CHANNEL)" == "yes" CHANNEL_PRO = proto/channel.pro CHANNEL_OBJ = $(OBJDIR)/channel.obj @@ -494,7 +511,7 @@ WINVER = 0x0501 #VIMRUNTIMEDIR = somewhere CFLAGS = -c /W3 /nologo $(CVARS) -I. -Iproto -DHAVE_PATHDEF -DWIN32 \ - $(CSCOPE_DEFS) $(TERM_DEFS) $(NETBEANS_DEFS) $(CHANNEL_DEFS) \ + $(CSCOPE_DEFS) $(TERM_DEFS) $(SOUND_DEFS) $(NETBEANS_DEFS) $(CHANNEL_DEFS) \ $(NBDEBUG_DEFS) $(XPM_DEFS) \ $(DEFINES) -DWINVER=$(WINVER) -D_WIN32_WINNT=$(WINVER) @@ -1217,7 +1234,7 @@ conflags = $(conflags) /map /mapinfo:lin LINKARGS1 = $(linkdebug) $(conflags) LINKARGS2 = $(CON_LIB) $(GUI_LIB) $(NODEFAULTLIB) $(LIBC) $(OLE_LIB) user32.lib \ $(LUA_LIB) $(MZSCHEME_LIB) $(PERL_LIB) $(PYTHON_LIB) $(PYTHON3_LIB) $(RUBY_LIB) \ - $(TCL_LIB) $(NETBEANS_LIB) $(XPM_LIB) $(LINK_PDB) + $(TCL_LIB) $(SOUND_LIB) $(NETBEANS_LIB) $(XPM_LIB) $(LINK_PDB) # Report link time code generation progress if used. !ifdef NODEBUG @@ -1253,12 +1270,12 @@ all: $(MAIN_TARGET) \ $(VIMDLLBASE).dll: $(OUTDIR) $(OBJ) $(XDIFF_OBJ) $(GUI_OBJ) $(CUI_OBJ) $(OLE_OBJ) $(OLE_IDL) $(MZSCHEME_OBJ) \ $(LUA_OBJ) $(PERL_OBJ) $(PYTHON_OBJ) $(PYTHON3_OBJ) $(RUBY_OBJ) $(TCL_OBJ) \ - $(CSCOPE_OBJ) $(TERM_OBJ) $(NETBEANS_OBJ) $(CHANNEL_OBJ) $(XPM_OBJ) \ + $(CSCOPE_OBJ) $(TERM_OBJ) $(SOUND_OBJ) $(NETBEANS_OBJ) $(CHANNEL_OBJ) $(XPM_OBJ) \ version.c version.h $(CC) $(CFLAGS_OUTDIR) version.c $(link) $(LINKARGS1) /dll -out:$(VIMDLLBASE).dll $(OBJ) $(XDIFF_OBJ) $(GUI_OBJ) $(CUI_OBJ) $(OLE_OBJ) \ $(LUA_OBJ) $(MZSCHEME_OBJ) $(PERL_OBJ) $(PYTHON_OBJ) $(PYTHON3_OBJ) $(RUBY_OBJ) \ - $(TCL_OBJ) $(CSCOPE_OBJ) $(TERM_OBJ) $(NETBEANS_OBJ) $(CHANNEL_OBJ) \ + $(TCL_OBJ) $(CSCOPE_OBJ) $(TERM_OBJ) $(SOUND_OBJ) $(NETBEANS_OBJ) $(CHANNEL_OBJ) \ $(XPM_OBJ) $(OUTDIR)\version.obj $(LINKARGS2) $(GVIM).exe: $(OUTDIR) $(EXEOBJG) $(VIMDLLBASE).dll @@ -1273,12 +1290,12 @@ all: $(MAIN_TARGET) \ $(VIM).exe: $(OUTDIR) $(OBJ) $(XDIFF_OBJ) $(GUI_OBJ) $(CUI_OBJ) $(OLE_OBJ) $(OLE_IDL) $(MZSCHEME_OBJ) \ $(LUA_OBJ) $(PERL_OBJ) $(PYTHON_OBJ) $(PYTHON3_OBJ) $(RUBY_OBJ) $(TCL_OBJ) \ - $(CSCOPE_OBJ) $(TERM_OBJ) $(NETBEANS_OBJ) $(CHANNEL_OBJ) $(XPM_OBJ) \ + $(CSCOPE_OBJ) $(TERM_OBJ) $(SOUND_OBJ) $(NETBEANS_OBJ) $(CHANNEL_OBJ) $(XPM_OBJ) \ version.c version.h $(CC) $(CFLAGS_OUTDIR) version.c $(link) $(LINKARGS1) /subsystem:$(SUBSYSTEM) -out:$(VIM).exe $(OBJ) $(XDIFF_OBJ) $(GUI_OBJ) $(CUI_OBJ) $(OLE_OBJ) \ $(LUA_OBJ) $(MZSCHEME_OBJ) $(PERL_OBJ) $(PYTHON_OBJ) $(PYTHON3_OBJ) $(RUBY_OBJ) \ - $(TCL_OBJ) $(CSCOPE_OBJ) $(TERM_OBJ) $(NETBEANS_OBJ) $(CHANNEL_OBJ) \ + $(TCL_OBJ) $(CSCOPE_OBJ) $(TERM_OBJ) $(SOUND_OBJ) $(NETBEANS_OBJ) $(CHANNEL_OBJ) \ $(XPM_OBJ) $(OUTDIR)\version.obj $(LINKARGS2) if exist $(VIM).exe.manifest mt.exe -nologo -manifest $(VIM).exe.manifest -updateresource:$(VIM).exe;1 @@ -1766,6 +1783,7 @@ proto.h: \ proto/usercmd.pro \ proto/userfunc.pro \ proto/window.pro \ + $(SOUND_PRO) \ $(NETBEANS_PRO) \ $(CHANNEL_PRO) diff --git a/src/sound.c b/src/sound.c --- a/src/sound.c +++ b/src/sound.c @@ -13,17 +13,18 @@ #include "vim.h" -#if (defined(FEAT_SOUND) && defined(HAVE_CANBERRA)) || defined(PROTO) - -#include +#if defined(FEAT_SOUND) || defined(PROTO) static long sound_id = 0; -static ca_context *context = NULL; typedef struct soundcb_S soundcb_T; struct soundcb_S { callback_T snd_callback; +#ifdef MSWIN + MCIDEVICEID snd_device_id; + long snd_id; +#endif soundcb_T *snd_next; }; @@ -75,6 +76,15 @@ delete_sound_callback(soundcb_T *soundcb } } +#if defined(HAVE_CANBERRA) || defined(PROTO) + +/* + * Sound implementation for Linux/Unix/Mac using libcanberra. + */ +# include + +static ca_context *context = NULL; + static void sound_callback( ca_context *c UNUSED, @@ -188,7 +198,7 @@ f_sound_clear(typval_T *argvars UNUSED, } } -#if defined(EXITFREE) || defined(PROTO) +# if defined(EXITFREE) || defined(PROTO) void sound_free(void) { @@ -197,6 +207,178 @@ sound_free(void) while (first_callback != NULL) delete_sound_callback(first_callback); } -#endif +# endif + +#elif defined(MSWIN) + +/* + * Sound implementation for MS-Windows. + */ + +static HWND g_hWndSound = NULL; + + static LRESULT CALLBACK +sound_wndproc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + soundcb_T *p; + + switch (message) + { + case MM_MCINOTIFY: + for (p = first_callback; p != NULL; p = p->snd_next) + if (p->snd_device_id == (MCIDEVICEID) lParam) + { + typval_T argv[3]; + typval_T rettv; + int dummy; + char buf[32]; + + vim_snprintf(buf, sizeof(buf), "close sound%06ld", + p->snd_id); + mciSendString(buf, NULL, 0, 0); + + argv[0].v_type = VAR_NUMBER; + argv[0].vval.v_number = p->snd_id; + argv[1].v_type = VAR_NUMBER; + argv[1].vval.v_number = + wParam == MCI_NOTIFY_SUCCESSFUL ? 0 + : wParam == MCI_NOTIFY_ABORTED ? 1 : 2; + argv[2].v_type = VAR_UNKNOWN; + + call_callback(&p->snd_callback, -1, + &rettv, 2, argv, NULL, 0L, 0L, &dummy, TRUE, NULL); + clear_tv(&rettv); + + delete_sound_callback(p); + redraw_after_callback(TRUE); + + } + break; + } + + return DefWindowProc(hwnd, message, wParam, lParam); +} + + static HWND +sound_window() +{ + if (g_hWndSound == NULL) + { + LPCSTR clazz = "VimSound"; + WNDCLASS wndclass = { + 0, sound_wndproc, 0, 0, g_hinst, NULL, 0, 0, NULL, clazz }; + RegisterClass(&wndclass); + g_hWndSound = CreateWindow(clazz, NULL, 0, 0, 0, 0, 0, + HWND_MESSAGE, NULL, g_hinst, NULL); + } + + return g_hWndSound; +} + + void +f_sound_playevent(typval_T *argvars, typval_T *rettv) +{ + WCHAR *wp; + + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + + wp = enc_to_utf16(tv_get_string(&argvars[0]), NULL); + if (wp == NULL) + return; + + PlaySoundW(wp, NULL, SND_ASYNC | SND_ALIAS); + free(wp); + + rettv->vval.v_number = ++sound_id; +} -#endif // FEAT_SOUND && HAVE_CANBERRA + void +f_sound_playfile(typval_T *argvars, typval_T *rettv) +{ + long newid = sound_id + 1; + size_t len; + char_u *p, *esc; + WCHAR *wp; + soundcb_T *soundcb; + char buf[32]; + MCIERROR err; + + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + + esc = vim_strsave_shellescape(tv_get_string(&argvars[0]), FALSE, FALSE); + + len = STRLEN(esc) + 5 + 18 + 1; + p = alloc(len); + if (p == NULL) + { + free(esc); + return; + } + vim_snprintf((char *)p, len, "open %s alias sound%06ld", esc, newid); + free(esc); + + wp = enc_to_utf16((char_u *)p, NULL); + free(p); + if (wp == NULL) + return; + + err = mciSendStringW(wp, NULL, 0, sound_window()); + free(wp); + if (err != 0) + return; + + vim_snprintf(buf, sizeof(buf), "play sound%06ld notify", newid); + err = mciSendString(buf, NULL, 0, sound_window()); + if (err != 0) + goto failure; + + sound_id = newid; + rettv->vval.v_number = sound_id; + + soundcb = get_sound_callback(&argvars[1]); + if (soundcb != NULL) + { + vim_snprintf(buf, sizeof(buf), "sound%06ld", newid); + soundcb->snd_id = newid; + soundcb->snd_device_id = mciGetDeviceID(buf); + } + return; + +failure: + vim_snprintf(buf, sizeof(buf), "close sound%06ld", newid); + mciSendString(buf, NULL, 0, NULL); +} + + void +f_sound_stop(typval_T *argvars, typval_T *rettv UNUSED) +{ + long id = tv_get_number(&argvars[0]); + char buf[32]; + + vim_snprintf(buf, sizeof(buf), "stop sound%06ld", id); + mciSendString(buf, NULL, 0, NULL); +} + + void +f_sound_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED) +{ + PlaySound(NULL, NULL, 0); + mciSendString("close all", NULL, 0, NULL); +} + +# if defined(EXITFREE) + void +sound_free(void) +{ + CloseWindow(g_hWndSound); + + while (first_callback != NULL) + delete_sound_callback(first_callback); +} +# endif + +#endif // MSWIN + +#endif // FEAT_SOUND diff --git a/src/testdir/test_sound.vim b/src/testdir/test_sound.vim --- a/src/testdir/test_sound.vim +++ b/src/testdir/test_sound.vim @@ -10,6 +10,10 @@ func PlayCallback(id, result) endfunc func Test_play_event() + if has('win32') + throw 'Skipped: Playing event with callback is not supported on Windows' + endif + let id = sound_playevent('bell', 'PlayCallback') if id == 0 throw 'Skipped: bell event not available' diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -778,6 +778,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1565, +/**/ 1564, /**/ 1563,