diff src/os_macosx.m @ 30719:71137f73c94d v9.0.0694

patch 9.0.0694: no native sound support on Mac OS Commit: https://github.com/vim/vim/commit/4314e4f7da4db5d85f63cdf43b73be3689502c93 Author: Yee Cheng Chin <ychin.git@gmail.com> Date: Sat Oct 8 13:50:05 2022 +0100 patch 9.0.0694: no native sound support on Mac OS Problem: No native sound support on Mac OS. Solution: Add sound support for Mac OS. (Yee Cheng Chin, closes https://github.com/vim/vim/issues/11274)
author Bram Moolenaar <Bram@vim.org>
date Sat, 08 Oct 2022 15:00:05 +0200
parents 3f88a6d02354
children 360f286b5869
line wrap: on
line diff
--- a/src/os_macosx.m
+++ b/src/os_macosx.m
@@ -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