xref: /vim-8.2.3635/src/sound.c (revision 4490ec4e)
1 /* vi:set ts=8 sts=4 sw=4 noet:
2  *
3  * VIM - Vi IMproved	by Bram Moolenaar
4  *
5  * Do ":help uganda"  in Vim to read copying and usage conditions.
6  * Do ":help credits" in Vim to see a list of people who contributed.
7  * See README.txt for an overview of the Vim source code.
8  */
9 
10 /*
11  * sound.c: functions related making noise
12  */
13 
14 #include "vim.h"
15 
16 #if defined(FEAT_SOUND) || defined(PROTO)
17 
18 static long	    sound_id = 0;
19 
20 typedef struct soundcb_S soundcb_T;
21 
22 struct soundcb_S {
23     callback_T	snd_callback;
24 #ifdef MSWIN
25     MCIDEVICEID	snd_device_id;
26     long	snd_id;
27 #endif
28     soundcb_T	*snd_next;
29 };
30 
31 static soundcb_T    *first_callback = NULL;
32 
33 /*
34  * Return TRUE when a sound callback has been created, it may be invoked when
35  * the sound finishes playing.  Also see has_sound_callback_in_queue().
36  */
37     int
has_any_sound_callback(void)38 has_any_sound_callback(void)
39 {
40     return first_callback != NULL;
41 }
42 
43     static soundcb_T *
get_sound_callback(typval_T * arg)44 get_sound_callback(typval_T *arg)
45 {
46     callback_T	callback;
47     soundcb_T	*soundcb;
48 
49     if (arg->v_type == VAR_UNKNOWN)
50 	return NULL;
51     callback = get_callback(arg);
52     if (callback.cb_name == NULL)
53 	return NULL;
54 
55     soundcb = ALLOC_ONE(soundcb_T);
56     if (soundcb == NULL)
57 	free_callback(&callback);
58     else
59     {
60 	soundcb->snd_next = first_callback;
61 	first_callback = soundcb;
62 	set_callback(&soundcb->snd_callback, &callback);
63     }
64     return soundcb;
65 }
66 
67 /*
68  * Delete "soundcb" from the list of pending callbacks.
69  */
70     static void
delete_sound_callback(soundcb_T * soundcb)71 delete_sound_callback(soundcb_T *soundcb)
72 {
73     soundcb_T	*p;
74     soundcb_T	*prev = NULL;
75 
76     for (p = first_callback; p != NULL; prev = p, p = p->snd_next)
77 	if (p == soundcb)
78 	{
79 	    if (prev == NULL)
80 		first_callback = p->snd_next;
81 	    else
82 		prev->snd_next = p->snd_next;
83 	    free_callback(&p->snd_callback);
84 	    vim_free(p);
85 	    break;
86 	}
87 }
88 
89 #if defined(HAVE_CANBERRA) || defined(PROTO)
90 
91 /*
92  * Sound implementation for Linux/Unix/Mac using libcanberra.
93  */
94 # include <canberra.h>
95 
96 static ca_context   *context = NULL;
97 
98 // Structure to store info about a sound callback to be invoked soon.
99 typedef struct soundcb_queue_S soundcb_queue_T;
100 
101 struct soundcb_queue_S {
102     soundcb_queue_T	*scb_next;
103     uint32_t		scb_id;		// ID of the sound
104     int			scb_result;	// CA_ value
105     soundcb_T		*scb_callback;	// function to call
106 };
107 
108 // Queue of callbacks to invoke from the main loop.
109 static soundcb_queue_T *callback_queue = NULL;
110 
111 /*
112  * Add a callback to the queue of callbacks to invoke later from the main loop.
113  * That is because the callback may be called from another thread and invoking
114  * another sound function may cause trouble.
115  */
116     static void
sound_callback(ca_context * c UNUSED,uint32_t id,int error_code,void * userdata)117 sound_callback(
118 	ca_context  *c UNUSED,
119 	uint32_t    id,
120 	int	    error_code,
121 	void	    *userdata)
122 {
123     soundcb_T	    *soundcb = (soundcb_T *)userdata;
124     soundcb_queue_T *scb;
125 
126     scb = ALLOC_ONE(soundcb_queue_T);
127     if (scb == NULL)
128 	return;
129     scb->scb_next = callback_queue;
130     callback_queue = scb;
131     scb->scb_id = id;
132     scb->scb_result = error_code == CA_SUCCESS ? 0
133 			  : error_code == CA_ERROR_CANCELED
134 					    || error_code == CA_ERROR_DESTROYED
135 			  ? 1 : 2;
136     scb->scb_callback = soundcb;
137 }
138 
139 /*
140  * Return TRUE if there is a sound callback to be called.
141  */
142     int
has_sound_callback_in_queue(void)143 has_sound_callback_in_queue(void)
144 {
145     return callback_queue != NULL;
146 }
147 
148 /*
149  * Invoke queued sound callbacks.
150  */
151     void
invoke_sound_callback(void)152 invoke_sound_callback(void)
153 {
154     soundcb_queue_T *scb;
155     typval_T	    argv[3];
156     typval_T	    rettv;
157 
158 
159     while (callback_queue != NULL)
160     {
161 	scb = callback_queue;
162 	callback_queue = scb->scb_next;
163 
164 	argv[0].v_type = VAR_NUMBER;
165 	argv[0].vval.v_number = scb->scb_id;
166 	argv[1].v_type = VAR_NUMBER;
167 	argv[1].vval.v_number = scb->scb_result;
168 	argv[2].v_type = VAR_UNKNOWN;
169 
170 	call_callback(&scb->scb_callback->snd_callback, -1, &rettv, 2, argv);
171 	clear_tv(&rettv);
172 
173 	delete_sound_callback(scb->scb_callback);
174 	vim_free(scb);
175     }
176     redraw_after_callback(TRUE);
177 }
178 
179     static void
sound_play_common(typval_T * argvars,typval_T * rettv,int playfile)180 sound_play_common(typval_T *argvars, typval_T *rettv, int playfile)
181 {
182     if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
183 	return;
184 
185     if (context == NULL)
186 	ca_context_create(&context);
187     if (context != NULL)
188     {
189 	soundcb_T	*soundcb = get_sound_callback(&argvars[1]);
190 	int		res = CA_ERROR_INVALID;
191 
192 	++sound_id;
193 	if (soundcb == NULL)
194 	{
195 	    res = ca_context_play(context, sound_id,
196 		    playfile ? CA_PROP_MEDIA_FILENAME : CA_PROP_EVENT_ID,
197 						    tv_get_string(&argvars[0]),
198 		    CA_PROP_CANBERRA_CACHE_CONTROL, "volatile",
199 		    NULL);
200 	}
201 	else
202 	{
203 	    static ca_proplist *proplist = NULL;
204 
205 	    ca_proplist_create(&proplist);
206 	    if (proplist != NULL)
207 	    {
208 		if (playfile)
209 		    ca_proplist_sets(proplist, CA_PROP_MEDIA_FILENAME,
210 					   (char *)tv_get_string(&argvars[0]));
211 		else
212 		    ca_proplist_sets(proplist, CA_PROP_EVENT_ID,
213 					   (char *)tv_get_string(&argvars[0]));
214 		ca_proplist_sets(proplist, CA_PROP_CANBERRA_CACHE_CONTROL,
215 			"volatile");
216 		res = ca_context_play_full(context, sound_id, proplist,
217 						      sound_callback, soundcb);
218 		if (res != CA_SUCCESS)
219 		    delete_sound_callback(soundcb);
220 
221 		ca_proplist_destroy(proplist);
222 	    }
223 	}
224 	rettv->vval.v_number = res == CA_SUCCESS ? sound_id : 0;
225     }
226 }
227 
228     void
f_sound_playevent(typval_T * argvars,typval_T * rettv)229 f_sound_playevent(typval_T *argvars, typval_T *rettv)
230 {
231     sound_play_common(argvars, rettv, FALSE);
232 }
233 
234 /*
235  * implementation of sound_playfile({path} [, {callback}])
236  */
237     void
f_sound_playfile(typval_T * argvars,typval_T * rettv)238 f_sound_playfile(typval_T *argvars, typval_T *rettv)
239 {
240     sound_play_common(argvars, rettv, TRUE);
241 }
242 
243 /*
244  * implementation of sound_stop({id})
245  */
246     void
f_sound_stop(typval_T * argvars,typval_T * rettv UNUSED)247 f_sound_stop(typval_T *argvars, typval_T *rettv UNUSED)
248 {
249     if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
250 	return;
251 
252     if (context != NULL)
253 	ca_context_cancel(context, tv_get_number(&argvars[0]));
254 }
255 
256 /*
257  * implementation of sound_clear()
258  */
259     void
f_sound_clear(typval_T * argvars UNUSED,typval_T * rettv UNUSED)260 f_sound_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
261 {
262     if (context != NULL)
263     {
264 	ca_context_destroy(context);
265 	context = NULL;
266     }
267 }
268 
269 # if defined(EXITFREE) || defined(PROTO)
270     void
sound_free(void)271 sound_free(void)
272 {
273     soundcb_queue_T *scb;
274 
275     if (context != NULL)
276 	ca_context_destroy(context);
277 
278     while (first_callback != NULL)
279 	delete_sound_callback(first_callback);
280 
281     while (callback_queue != NULL)
282     {
283 	scb = callback_queue;
284 	callback_queue = scb->scb_next;
285 	delete_sound_callback(scb->scb_callback);
286 	vim_free(scb);
287     }
288 }
289 # endif
290 
291 #elif defined(MSWIN)
292 
293 /*
294  * Sound implementation for MS-Windows.
295  */
296 
297 static HWND g_hWndSound = NULL;
298 
299     static LRESULT CALLBACK
sound_wndproc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)300 sound_wndproc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
301 {
302     soundcb_T	*p;
303 
304     switch (message)
305     {
306 	case MM_MCINOTIFY:
307 	    for (p = first_callback; p != NULL; p = p->snd_next)
308 		if (p->snd_device_id == (MCIDEVICEID) lParam)
309 		{
310 		    typval_T	argv[3];
311 		    typval_T	rettv;
312 		    char	buf[32];
313 
314 		    vim_snprintf(buf, sizeof(buf), "close sound%06ld",
315 								p->snd_id);
316 		    mciSendString(buf, NULL, 0, 0);
317 
318 		    argv[0].v_type = VAR_NUMBER;
319 		    argv[0].vval.v_number = p->snd_id;
320 		    argv[1].v_type = VAR_NUMBER;
321 		    argv[1].vval.v_number =
322 					wParam == MCI_NOTIFY_SUCCESSFUL ? 0
323 				      : wParam == MCI_NOTIFY_ABORTED ? 1 : 2;
324 		    argv[2].v_type = VAR_UNKNOWN;
325 
326 		    call_callback(&p->snd_callback, -1, &rettv, 2, argv);
327 		    clear_tv(&rettv);
328 
329 		    delete_sound_callback(p);
330 		    redraw_after_callback(TRUE);
331 
332 		}
333 	    break;
334     }
335 
336     return DefWindowProc(hwnd, message, wParam, lParam);
337 }
338 
339     static HWND
sound_window()340 sound_window()
341 {
342     if (g_hWndSound == NULL)
343     {
344 	LPCSTR clazz = "VimSound";
345 	WNDCLASS wndclass = {
346 	    0, sound_wndproc, 0, 0, g_hinst, NULL, 0, 0, NULL, clazz };
347 	RegisterClass(&wndclass);
348 	g_hWndSound = CreateWindow(clazz, NULL, 0, 0, 0, 0, 0,
349 		HWND_MESSAGE, NULL, g_hinst, NULL);
350     }
351 
352     return g_hWndSound;
353 }
354 
355     void
f_sound_playevent(typval_T * argvars,typval_T * rettv)356 f_sound_playevent(typval_T *argvars, typval_T *rettv)
357 {
358     WCHAR	    *wp;
359 
360     if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
361 	return;
362 
363     wp = enc_to_utf16(tv_get_string(&argvars[0]), NULL);
364     if (wp == NULL)
365 	return;
366 
367     if (PlaySoundW(wp, NULL, SND_ASYNC | SND_ALIAS))
368 	rettv->vval.v_number = ++sound_id;
369     free(wp);
370 }
371 
372     void
f_sound_playfile(typval_T * argvars,typval_T * rettv)373 f_sound_playfile(typval_T *argvars, typval_T *rettv)
374 {
375     long	newid = sound_id + 1;
376     size_t	len;
377     char_u	*p, *esc;
378     WCHAR	*wp;
379     soundcb_T	*soundcb;
380     char	buf[32];
381     MCIERROR	err;
382 
383     if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
384 	return;
385 
386     esc = vim_strsave_shellescape(tv_get_string(&argvars[0]), FALSE, FALSE);
387 
388     len = STRLEN(esc) + 5 + 18 + 1;
389     p = alloc(len);
390     if (p == NULL)
391     {
392 	free(esc);
393 	return;
394     }
395     vim_snprintf((char *)p, len, "open %s alias sound%06ld", esc, newid);
396     free(esc);
397 
398     wp = enc_to_utf16((char_u *)p, NULL);
399     free(p);
400     if (wp == NULL)
401 	return;
402 
403     err = mciSendStringW(wp, NULL, 0, sound_window());
404     free(wp);
405     if (err != 0)
406 	return;
407 
408     vim_snprintf(buf, sizeof(buf), "play sound%06ld notify", newid);
409     err = mciSendString(buf, NULL, 0, sound_window());
410     if (err != 0)
411 	goto failure;
412 
413     sound_id = newid;
414     rettv->vval.v_number = sound_id;
415 
416     soundcb = get_sound_callback(&argvars[1]);
417     if (soundcb != NULL)
418     {
419 	vim_snprintf(buf, sizeof(buf), "sound%06ld", newid);
420 	soundcb->snd_id = newid;
421 	soundcb->snd_device_id = mciGetDeviceID(buf);
422     }
423     return;
424 
425 failure:
426     vim_snprintf(buf, sizeof(buf), "close sound%06ld", newid);
427     mciSendString(buf, NULL, 0, NULL);
428 }
429 
430     void
f_sound_stop(typval_T * argvars,typval_T * rettv UNUSED)431 f_sound_stop(typval_T *argvars, typval_T *rettv UNUSED)
432 {
433     long    id;
434     char    buf[32];
435 
436     if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
437 	return;
438 
439     id = tv_get_number(&argvars[0]);
440     vim_snprintf(buf, sizeof(buf), "stop sound%06ld", id);
441     mciSendString(buf, NULL, 0, NULL);
442 }
443 
444     void
f_sound_clear(typval_T * argvars UNUSED,typval_T * rettv UNUSED)445 f_sound_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
446 {
447     PlaySoundW(NULL, NULL, 0);
448     mciSendString("close all", NULL, 0, NULL);
449 }
450 
451 # if defined(EXITFREE)
452     void
sound_free(void)453 sound_free(void)
454 {
455     CloseWindow(g_hWndSound);
456 
457     while (first_callback != NULL)
458 	delete_sound_callback(first_callback);
459 }
460 # endif
461 
462 #endif // MSWIN
463 
464 #endif  // FEAT_SOUND
465