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 #11274)
This commit is contained in:
Yee Cheng Chin
2022-10-08 13:50:05 +01:00
committed by Bram Moolenaar
parent 5a049846e4
commit 4314e4f7da
14 changed files with 264 additions and 35 deletions

View 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

View 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])

View File

@ -484,7 +484,7 @@
#endif
/*
* sound - currently only with libcanberra
* sound
*/
#if !defined(FEAT_SOUND) && defined(HAVE_CANBERRA)
# define FEAT_SOUND

View 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();

View 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

View 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())

View 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);

7
src/proto/os_macosx.pro Normal file
View File

@ -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 : */

View 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);

View 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
long result = 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);
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

View 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

View File

@ -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
))

View File

@ -699,6 +699,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
694,
/**/
693,
/**/

View File

@ -163,9 +163,16 @@
*/
#include "feature.h"
#if defined(MACOS_X_DARWIN) && defined(FEAT_NORMAL) \
&& !defined(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.