]> granicus.if.org Git - vim/commitdiff
patch 9.0.0694: no native sound support on Mac OS v9.0.0694
authorYee Cheng Chin <ychin.git@gmail.com>
Sat, 8 Oct 2022 12:50:05 +0000 (13:50 +0100)
committerBram Moolenaar <Bram@vim.org>
Sat, 8 Oct 2022 12:50:05 +0000 (13:50 +0100)
Problem:    No native sound support on Mac OS.
Solution:   Add sound support for Mac OS. (Yee Cheng Chin, closes #11274)

14 files changed:
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 [new file with mode: 0644]
src/proto/sound.pro
src/sound.c
src/testdir/test_sound.vim
src/ui.c
src/version.c
src/vim.h

index 2ed73999cc1012db160aaf73c4ef34ee2636ba72..0cba7c215faee186b89e7b65a2d87e013cd36a26 100644 (file)
@@ -8631,6 +8631,9 @@ sound_playevent({name} [, {callback}])
 <              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
index a588ad6e5fecce98ebc21147df66e80e516cfe91..e203fde84cdc0fe6ca1154447ae6e530c41affbe 100644 (file)
@@ -4553,7 +4553,7 @@ if test "$MACOS_X" = "yes"; then
   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.
+      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])
index bc259fd562ff480b6faeb9c27832c3d727c4c3b7..2c65253a04d85a38f61761372c185014c4248c04 100644 (file)
 #endif
 
 /*
- * sound - currently only with libcanberra
+ * sound
  */
 #if !defined(FEAT_SOUND) && defined(HAVE_CANBERRA)
 # define FEAT_SOUND
index 10f7f0e152e83b95e2de7da812399a01707634fd..d1d3fc6823325fcb32f9a57f05baab501c08beb3 100644 (file)
@@ -2326,6 +2326,10 @@ parse_queued_messages(void)
 # 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();
index f666b71644c0473504e6f59a5d60bb794988711c..84a0def9e0973a6a82fd10561c11daed3fd1590d 100644 (file)
@@ -384,6 +384,139 @@ timer_delete(timer_t timerid)
 
 #endif /* FEAT_RELTIME */
 
+#ifdef FEAT_SOUND
+
+static NSMutableDictionary<NSNumber*, NSSound*> *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<NSSoundDelegate>;
+
+- (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<NSNumber*, NSSound*> 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
index 145f93f35bf53787b7d6e024b60f7b9b909a14a6..814f3ad5742043d5304cd5c06c6dff9216e63da7 100644 (file)
@@ -6125,6 +6125,10 @@ WaitForCharOrMouse(long msec, int *interrupted, int ignore_input)
                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())
index 9638eaaf6645c04d17f3cb75fab7b64daf39a336..7ddf1037b4857e6a5b2fcc8316cb79a5240c5b38 100644 (file)
@@ -327,6 +327,9 @@ extern char_u *vimpty_getenv(const char_u *string); // in misc2.c
 # 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);
diff --git a/src/proto/os_macosx.pro b/src/proto/os_macosx.pro
new file mode 100644 (file)
index 0000000..5b96b75
--- /dev/null
@@ -0,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 : */
index ad1c2b5305399f6137f865907aa8290362abf333..c6d0541f98750bbe9b5c6bf8d97c22124061bb79 100644 (file)
@@ -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);
index 2f121eb470c1716a7f2708575d3e2d0867177679..f0f5ab0d11b51dcfe8541de8f023dee21a3de2a1 100644 (file)
@@ -64,10 +64,29 @@ get_sound_callback(typval_T *arg)
     return soundcb;
 }
 
+/*
+ * 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.
  */
-    static void
+    void
 delete_sound_callback(soundcb_T *soundcb)
 {
     soundcb_T  *p;
@@ -89,7 +108,7 @@ delete_sound_callback(soundcb_T *soundcb)
 #if defined(HAVE_CANBERRA) || defined(PROTO)
 
 /*
- * Sound implementation for Linux/Unix/Mac using libcanberra.
+ * Sound implementation for Linux/Unix using libcanberra.
  */
 # include <canberra.h>
 
@@ -152,23 +171,13 @@ has_sound_callback_in_queue(void)
 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);
+       call_sound_callback(scb->scb_callback, scb->scb_id, scb->scb_result);
 
        delete_sound_callback(scb->scb_callback);
        vim_free(scb);
@@ -307,24 +316,15 @@ sound_wndproc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
            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);
+                   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,6 +459,64 @@ sound_free(void)
 }
 # endif
 
-#endif // MSWIN
+#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
index ff58262bff0ea2e8c60c26c59a317ac8cfb8a09d..e97ac6198b23cfb650574e8f82ac6a316681722c 100644 (file)
@@ -17,7 +17,11 @@ func Test_play_event()
   endif
   let g:playcallback_count = 0
   let g:id = 0
-  let id = 'bell'->sound_playevent('PlayCallback')
+  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
index 8368c30540ec06004e16130e6912815e1c621134..b56e3952503ea4f4a349c84a10042f845ecab0c2 100644 (file)
--- a/src/ui.c
+++ b/src/ui.c
@@ -460,7 +460,7 @@ ui_wait_for_chars_or_timer(
        }
        if (due_time <= 0 || (wtime > 0 && due_time > remaining))
            due_time = remaining;
-# if defined(FEAT_JOB_CHANNEL) || defined(FEAT_SOUND_CANBERRA)
+# 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,11 +468,11 @@ ui_wait_for_chars_or_timer(
                !gui.in_use &&
 #   endif
                (has_pending_job() || channel_any_readahead()))
-#   ifdef FEAT_SOUND_CANBERRA
+#   if defined(FEAT_SOUND_CANBERRA) || defined(FEAT_SOUND_MACOSX)
                ||
 #   endif
 #  endif
-#  ifdef FEAT_SOUND_CANBERRA
+#  if defined(FEAT_SOUND_CANBERRA) ||  defined(FEAT_SOUND_MACOSX)
                    has_any_sound_callback()
 #  endif
                    ))
index 86099f4ffda4f93488b2cd5183cbee968978dee8..6813bf6bb7c7c6e1ce67c832e6c1816a7b12404a 100644 (file)
@@ -699,6 +699,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    694,
 /**/
     693,
 /**/
index 4a77decc88b9d47e8a8179c77792db646a0f088f..57368f3b402b8e440e4c6e42e52aed58ec131205 100644 (file)
--- a/src/vim.h
+++ b/src/vim.h
  */
 #include "feature.h"
 
-#if defined(MACOS_X_DARWIN) && defined(FEAT_NORMAL) \
-       && !defined(FEAT_CLIPBOARD)
-# define FEAT_CLIPBOARD
+#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.