To: vim_dev@googlegroups.com Subject: Patch 9.0.0694 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 9.0.0694 Problem: No native sound support on Mac OS. Solution: Add sound support for Mac OS. (Yee Cheng Chin, closes #11274) Files: runtime/doc/builtin.txt, src/configure.ac, src/feature.h, src/getchar.c, src/os_macosx.m, src/os_unix.c, src/proto.h, src/proto/os_macosx.pro, src/proto/sound.pro, src/sound.c, src/ui.c, src/vim.h, src/testdir/test_sound.vim *** ../vim-9.0.0693/runtime/doc/builtin.txt 2022-10-07 14:31:04.316852643 +0100 --- runtime/doc/builtin.txt 2022-10-08 13:35:32.135921536 +0100 *************** *** 8612,8617 **** --- 8631,8639 ---- < On MS-Windows, {name} can be SystemAsterisk, SystemDefault, SystemExclamation, SystemExit, SystemHand, SystemQuestion, SystemStart, SystemWelcome, etc. + On macOS, {name} refers to files located in + /System/Library/Sounds (e.g. "Tink"). It will also work for + custom installed sounds in folders like ~/Library/Sounds. When {callback} is specified it is invoked when the sound is finished. The first argument is the sound ID, the second *** ../vim-9.0.0693/src/configure.ac 2022-10-04 16:23:39.006042192 +0100 --- src/configure.ac 2022-10-08 13:38:23.911150094 +0100 *************** *** 4553,4559 **** AC_MSG_CHECKING([whether we need macOS frameworks]) if test "$MACOS_X_DARWIN" = "yes"; then if test "$features" = "tiny"; then ! dnl Since no FEAT_CLIPBOARD, no longer need for os_macosx.m. OS_EXTRA_SRC=`echo "$OS_EXTRA_SRC" | sed -e 's+os_macosx.m++'` OS_EXTRA_OBJ=`echo "$OS_EXTRA_OBJ" | sed -e 's+objects/os_macosx.o++'` AC_MSG_RESULT([yes, we need CoreServices]) --- 4553,4559 ---- AC_MSG_CHECKING([whether we need macOS frameworks]) if test "$MACOS_X_DARWIN" = "yes"; then if test "$features" = "tiny"; then ! dnl Since no FEAT_CLIPBOARD or FEAT_SOUND, no need for os_macosx.m. OS_EXTRA_SRC=`echo "$OS_EXTRA_SRC" | sed -e 's+os_macosx.m++'` OS_EXTRA_OBJ=`echo "$OS_EXTRA_OBJ" | sed -e 's+objects/os_macosx.o++'` AC_MSG_RESULT([yes, we need CoreServices]) *** ../vim-9.0.0693/src/feature.h 2022-10-04 16:28:08.169719793 +0100 --- src/feature.h 2022-10-08 13:35:32.139921518 +0100 *************** *** 484,490 **** #endif /* ! * sound - currently only with libcanberra */ #if !defined(FEAT_SOUND) && defined(HAVE_CANBERRA) # define FEAT_SOUND --- 484,490 ---- #endif /* ! * sound */ #if !defined(FEAT_SOUND) && defined(HAVE_CANBERRA) # define FEAT_SOUND *** ../vim-9.0.0693/src/getchar.c 2022-10-04 16:23:39.014042183 +0100 --- src/getchar.c 2022-10-08 13:35:32.139921518 +0100 *************** *** 2326,2331 **** --- 2326,2335 ---- # ifdef FEAT_TERMINAL free_unused_terminals(); # endif + + # ifdef FEAT_SOUND_MACOSX + process_cfrunloop(); + # endif # ifdef FEAT_SOUND_CANBERRA if (has_sound_callback_in_queue()) invoke_sound_callback(); *** ../vim-9.0.0693/src/os_macosx.m 2022-08-21 17:23:57.131150493 +0100 --- src/os_macosx.m 2022-10-08 13:35:32.139921518 +0100 *************** *** 384,389 **** --- 384,522 ---- #endif /* FEAT_RELTIME */ + #ifdef FEAT_SOUND + + static NSMutableDictionary *sounds_list = nil; + + /// A delegate for handling when a sound has stopped playing, in + /// order to clean up the sound and to send a callback. + @interface SoundDelegate : NSObject; + + - (id) init:(long) sound_id callback:(soundcb_T*) callback; + - (void) sound:(NSSound *)sound didFinishPlaying:(BOOL)flag; + + @property (readonly) long sound_id; + @property (readonly) soundcb_T *callback; + + @end + + @implementation SoundDelegate + - (id) init:(long) sound_id callback:(soundcb_T*) callback + { + if ([super init]) + { + _sound_id = sound_id; + _callback = callback; + } + return self; + } + + - (void) sound:(NSSound *)sound didFinishPlaying:(BOOL)flag + { + if (sounds_list != nil) + { + if (_callback) + { + call_sound_callback(_callback, _sound_id, flag ? 0 : 1); + delete_sound_callback(_callback); + _callback = NULL; + } + [sounds_list removeObjectForKey:[NSNumber numberWithLong:_sound_id]]; + } + // Release itself. Do that here instead of earlier because NSSound only + // holds weak reference to this object. + [self release]; + } + @end + + void + process_cfrunloop() + { + if (sounds_list != nil && [sounds_list count] > 0) + { + // Continually drain the run loop of events. Currently, this + // is only used for processing sound callbacks, because + // NSSound relies of this runloop to call back to the + // delegate. + @autoreleasepool + { + while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) + == kCFRunLoopRunHandledSource) + ; // do nothing + } + } + } + + bool + sound_mch_play(const char_u* sound_name, long sound_id, soundcb_T *callback, bool playfile) + { + @autoreleasepool + { + NSString *sound_name_ns = [[[NSString alloc] initWithUTF8String:(const char*)sound_name] autorelease]; + NSSound* sound = playfile ? + [[[NSSound alloc] initWithContentsOfFile:sound_name_ns byReference:YES] autorelease] : + [NSSound soundNamed:sound_name_ns]; + if (!sound) + { + return false; + } + + if (sounds_list == nil) + { + sounds_list = [[NSMutableDictionary alloc] init]; + } + sounds_list[[NSNumber numberWithLong:sound_id]] = sound; + + // Make a delegate to handle when the sound stops. No need to call + // autorelease because NSSound only holds a weak reference to it. + SoundDelegate *delegate = [[SoundDelegate alloc] init:sound_id callback:callback]; + + [sound setDelegate:delegate]; + [sound play]; + } + return true; + } + + void + sound_mch_stop(long sound_id) + { + @autoreleasepool + { + NSSound *sound = sounds_list[[NSNumber numberWithLong:sound_id]]; + if (sound != nil) + { + // Stop the sound. No need to release it because the delegate will do + // it for us. + [sound stop]; + } + } + } + + void + sound_mch_clear() + { + if (sounds_list != nil) + { + @autoreleasepool + { + for (NSSound *sound in [sounds_list allValues]) + { + [sound stop]; + } + [sounds_list release]; + sounds_list = nil; + } + } + } + + void + sound_mch_free() + { + sound_mch_clear(); + } + + #endif // FEAT_SOUND + /* Lift the compiler warning suppression. */ #if defined(__clang__) && defined(__STRICT_ANSI__) # pragma clang diagnostic pop *** ../vim-9.0.0693/src/os_unix.c 2022-10-04 13:17:27.496307898 +0100 --- src/os_unix.c 2022-10-08 13:35:32.139921518 +0100 *************** *** 6125,6130 **** --- 6125,6134 ---- rest -= msec; } # endif + # ifdef FEAT_SOUND_MACOSX + // Invoke any pending sound callbacks. + process_cfrunloop(); + # endif # ifdef FEAT_SOUND_CANBERRA // Invoke any pending sound callbacks. if (has_sound_callback_in_queue()) *** ../vim-9.0.0693/src/proto.h 2022-04-03 17:11:32.000000000 +0100 --- src/proto.h 2022-10-08 13:35:32.139921518 +0100 *************** *** 327,332 **** --- 327,335 ---- # ifdef MACOS_CONVERT # include "os_mac_conv.pro" # endif + # ifdef MACOS_X + # include "os_macosx.pro" + # endif # if defined(MACOS_X_DARWIN) && defined(FEAT_CLIPBOARD) && !defined(FEAT_GUI) // functions in os_macosx.m void clip_mch_lose_selection(Clipboard_T *cbd); *** ../vim-9.0.0693/src/proto/os_macosx.pro 2022-10-08 13:48:16.721528655 +0100 --- src/proto/os_macosx.pro 2022-10-08 13:35:32.139921518 +0100 *************** *** 0 **** --- 1,7 ---- + /* os_macosx.m */ + void process_cfrunloop(); + bool sound_mch_play(const char_u* event, long sound_id, soundcb_T *callback, bool playfile); + void sound_mch_stop(long sound_id); + void sound_mch_clear(); + void sound_mch_free(); + /* vim: set ft=c : */ *** ../vim-9.0.0693/src/proto/sound.pro 2022-06-27 23:15:22.000000000 +0100 --- src/proto/sound.pro 2022-10-08 13:35:32.139921518 +0100 *************** *** 1,6 **** --- 1,10 ---- /* sound.c */ + typedef struct soundcb_S soundcb_T; + int has_any_sound_callback(void); int has_sound_callback_in_queue(void); + void call_sound_callback(soundcb_T *soundcb, long sound_id, int result); + void delete_sound_callback(soundcb_T *soundcb); void invoke_sound_callback(void); void f_sound_playevent(typval_T *argvars, typval_T *rettv); void f_sound_playfile(typval_T *argvars, typval_T *rettv); *** ../vim-9.0.0693/src/sound.c 2021-12-09 10:14:30.000000000 +0000 --- src/sound.c 2022-10-08 13:35:32.139921518 +0100 *************** *** 65,73 **** } /* * Delete "soundcb" from the list of pending callbacks. */ ! static void delete_sound_callback(soundcb_T *soundcb) { soundcb_T *p; --- 65,92 ---- } /* + * Call "soundcb" with proper parameters. + */ + void + call_sound_callback(soundcb_T *soundcb, long snd_id, int result) + { + typval_T argv[3]; + typval_T rettv; + + argv[0].v_type = VAR_NUMBER; + argv[0].vval.v_number = snd_id; + argv[1].v_type = VAR_NUMBER; + argv[1].vval.v_number = result; + argv[2].v_type = VAR_UNKNOWN; + + call_callback(&soundcb->snd_callback, -1, &rettv, 2, argv); + clear_tv(&rettv); + } + + /* * Delete "soundcb" from the list of pending callbacks. */ ! void delete_sound_callback(soundcb_T *soundcb) { soundcb_T *p; *************** *** 89,95 **** #if defined(HAVE_CANBERRA) || defined(PROTO) /* ! * Sound implementation for Linux/Unix/Mac using libcanberra. */ # include --- 108,114 ---- #if defined(HAVE_CANBERRA) || defined(PROTO) /* ! * Sound implementation for Linux/Unix using libcanberra. */ # include *************** *** 152,174 **** invoke_sound_callback(void) { soundcb_queue_T *scb; - typval_T argv[3]; - typval_T rettv; - while (callback_queue != NULL) { scb = callback_queue; callback_queue = scb->scb_next; ! argv[0].v_type = VAR_NUMBER; ! argv[0].vval.v_number = scb->scb_id; ! argv[1].v_type = VAR_NUMBER; ! argv[1].vval.v_number = scb->scb_result; ! argv[2].v_type = VAR_UNKNOWN; ! ! call_callback(&scb->scb_callback->snd_callback, -1, &rettv, 2, argv); ! clear_tv(&rettv); delete_sound_callback(scb->scb_callback); vim_free(scb); --- 171,183 ---- invoke_sound_callback(void) { soundcb_queue_T *scb; while (callback_queue != NULL) { scb = callback_queue; callback_queue = scb->scb_next; ! call_sound_callback(scb->scb_callback, scb->scb_id, scb->scb_result); delete_sound_callback(scb->scb_callback); vim_free(scb); *************** *** 307,330 **** for (p = first_callback; p != NULL; p = p->snd_next) if (p->snd_device_id == (MCIDEVICEID) lParam) { - typval_T argv[3]; - typval_T rettv; 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); ! clear_tv(&rettv); delete_sound_callback(p); redraw_after_callback(TRUE, FALSE); --- 316,330 ---- for (p = first_callback; p != NULL; p = p->snd_next) if (p->snd_device_id == (MCIDEVICEID) lParam) { char buf[32]; vim_snprintf(buf, sizeof(buf), "close sound%06ld", p->snd_id); mciSendString(buf, NULL, 0, 0); ! long result = wParam == MCI_NOTIFY_SUCCESSFUL ? 0 ! : wParam == MCI_NOTIFY_ABORTED ? 1 : 2; ! call_sound_callback(p, p->snd_id, result); delete_sound_callback(p); redraw_after_callback(TRUE, FALSE); *************** *** 459,464 **** } # endif ! #endif // MSWIN #endif // FEAT_SOUND --- 459,522 ---- } # endif ! #elif defined(MACOS_X_DARWIN) ! ! // Sound implementation for macOS. ! static void ! sound_play_common(typval_T *argvars, typval_T *rettv, bool playfile) ! { ! if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL) ! return; ! ! char_u *sound_name = tv_get_string(&argvars[0]); ! soundcb_T *soundcb = get_sound_callback(&argvars[1]); ! ! ++sound_id; ! ! bool play_success = sound_mch_play(sound_name, sound_id, soundcb, playfile); ! if (!play_success && soundcb) ! { ! delete_sound_callback(soundcb); ! } ! rettv->vval.v_number = play_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 (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL) ! return; ! sound_mch_stop(tv_get_number(&argvars[0])); ! } ! ! void ! f_sound_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED) ! { ! sound_mch_clear(); ! } ! ! #if defined(EXITFREE) || defined(PROTO) ! void ! sound_free(void) ! { ! sound_mch_free(); ! while (first_callback != NULL) ! delete_sound_callback(first_callback); ! } ! #endif ! ! #endif // MACOS_X_DARWIN #endif // FEAT_SOUND *** ../vim-9.0.0693/src/ui.c 2022-06-10 14:49:43.000000000 +0100 --- src/ui.c 2022-10-08 13:35:32.139921518 +0100 *************** *** 460,466 **** } if (due_time <= 0 || (wtime > 0 && due_time > remaining)) due_time = remaining; ! # if defined(FEAT_JOB_CHANNEL) || defined(FEAT_SOUND_CANBERRA) if ((due_time < 0 || due_time > 10L) && ( # if defined(FEAT_JOB_CHANNEL) ( --- 460,466 ---- } if (due_time <= 0 || (wtime > 0 && due_time > remaining)) due_time = remaining; ! # if defined(FEAT_JOB_CHANNEL) || defined(FEAT_SOUND_CANBERRA) || defined(FEAT_SOUND_MACOSX) if ((due_time < 0 || due_time > 10L) && ( # if defined(FEAT_JOB_CHANNEL) ( *************** *** 468,478 **** !gui.in_use && # endif (has_pending_job() || channel_any_readahead())) ! # ifdef FEAT_SOUND_CANBERRA || # endif # endif ! # ifdef FEAT_SOUND_CANBERRA has_any_sound_callback() # endif )) --- 468,478 ---- !gui.in_use && # endif (has_pending_job() || channel_any_readahead())) ! # if defined(FEAT_SOUND_CANBERRA) || defined(FEAT_SOUND_MACOSX) || # endif # endif ! # if defined(FEAT_SOUND_CANBERRA) || defined(FEAT_SOUND_MACOSX) has_any_sound_callback() # endif )) *** ../vim-9.0.0693/src/vim.h 2022-10-04 16:50:15.663997565 +0100 --- src/vim.h 2022-10-08 13:35:32.139921518 +0100 *************** *** 163,171 **** */ #include "feature.h" ! #if defined(MACOS_X_DARWIN) && defined(FEAT_NORMAL) \ ! && !defined(FEAT_CLIPBOARD) ! # define FEAT_CLIPBOARD #endif // +x11 is only enabled when it's both available and wanted. --- 163,178 ---- */ #include "feature.h" ! #if defined(MACOS_X_DARWIN) ! # if defined(FEAT_NORMAL) && !defined(FEAT_CLIPBOARD) ! # define FEAT_CLIPBOARD ! # endif ! # if defined(FEAT_BIG) && !defined(FEAT_SOUND) ! # define FEAT_SOUND ! # endif ! # if defined(FEAT_SOUND) ! # define FEAT_SOUND_MACOSX ! # endif #endif // +x11 is only enabled when it's both available and wanted. *** ../vim-9.0.0693/src/testdir/test_sound.vim 2021-06-07 19:28:19.000000000 +0100 --- src/testdir/test_sound.vim 2022-10-08 13:35:32.139921518 +0100 *************** *** 17,23 **** endif let g:playcallback_count = 0 let g:id = 0 ! let id = 'bell'->sound_playevent('PlayCallback') if id == 0 throw 'Skipped: bell event not available' endif --- 17,27 ---- endif let g:playcallback_count = 0 let g:id = 0 ! let event_name = 'bell' ! if has('osx') ! let event_name = 'Tink' ! endif ! let id = event_name->sound_playevent('PlayCallback') if id == 0 throw 'Skipped: bell event not available' endif *** ../vim-9.0.0693/src/version.c 2022-10-08 12:52:04.317689786 +0100 --- src/version.c 2022-10-08 13:37:30.991385168 +0100 *************** *** 701,702 **** --- 701,704 ---- { /* Add new patch number below this line */ + /**/ + 694, /**/ -- Not too long ago, a keyboard was something to make music with... /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///