xref: /vim-8.2.3635/src/sound.c (revision ed37d9b3)
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
38 has_any_sound_callback(void)
39 {
40     return first_callback != NULL;
41 }
42 
43     static soundcb_T *
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
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
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
143 has_sound_callback_in_queue(void)
144 {
145     return callback_queue != NULL;
146 }
147 
148 /*
149  * Invoke queued sound callbacks.
150  */
151     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
180 sound_play_common(typval_T *argvars, typval_T *rettv, int playfile)
181 {
182     if (context == NULL)
183 	ca_context_create(&context);
184     if (context != NULL)
185     {
186 	soundcb_T	*soundcb = get_sound_callback(&argvars[1]);
187 	int		res = CA_ERROR_INVALID;
188 
189 	++sound_id;
190 	if (soundcb == NULL)
191 	{
192 	    res = ca_context_play(context, sound_id,
193 		    playfile ? CA_PROP_MEDIA_FILENAME : CA_PROP_EVENT_ID,
194 						    tv_get_string(&argvars[0]),
195 		    CA_PROP_CANBERRA_CACHE_CONTROL, "volatile",
196 		    NULL);
197 	}
198 	else
199 	{
200 	    static ca_proplist *proplist = NULL;
201 
202 	    ca_proplist_create(&proplist);
203 	    if (proplist != NULL)
204 	    {
205 		if (playfile)
206 		    ca_proplist_sets(proplist, CA_PROP_MEDIA_FILENAME,
207 					   (char *)tv_get_string(&argvars[0]));
208 		else
209 		    ca_proplist_sets(proplist, CA_PROP_EVENT_ID,
210 					   (char *)tv_get_string(&argvars[0]));
211 		ca_proplist_sets(proplist, CA_PROP_CANBERRA_CACHE_CONTROL,
212 			"volatile");
213 		res = ca_context_play_full(context, sound_id, proplist,
214 						      sound_callback, soundcb);
215 		if (res != CA_SUCCESS)
216 		    delete_sound_callback(soundcb);
217 
218 		ca_proplist_destroy(proplist);
219 	    }
220 	}
221 	rettv->vval.v_number = res == CA_SUCCESS ? sound_id : 0;
222     }
223 }
224 
225     void
226 f_sound_playevent(typval_T *argvars, typval_T *rettv)
227 {
228     sound_play_common(argvars, rettv, FALSE);
229 }
230 
231 /*
232  * implementation of sound_playfile({path} [, {callback}])
233  */
234     void
235 f_sound_playfile(typval_T *argvars, typval_T *rettv)
236 {
237     sound_play_common(argvars, rettv, TRUE);
238 }
239 
240 /*
241  * implementation of sound_stop({id})
242  */
243     void
244 f_sound_stop(typval_T *argvars, typval_T *rettv UNUSED)
245 {
246     if (context != NULL)
247 	ca_context_cancel(context, tv_get_number(&argvars[0]));
248 }
249 
250 /*
251  * implementation of sound_clear()
252  */
253     void
254 f_sound_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
255 {
256     if (context != NULL)
257     {
258 	ca_context_destroy(context);
259 	context = NULL;
260     }
261 }
262 
263 # if defined(EXITFREE) || defined(PROTO)
264     void
265 sound_free(void)
266 {
267     soundcb_queue_T *scb;
268 
269     if (context != NULL)
270 	ca_context_destroy(context);
271 
272     while (first_callback != NULL)
273 	delete_sound_callback(first_callback);
274 
275     while (callback_queue != NULL)
276     {
277 	scb = callback_queue;
278 	callback_queue = scb->scb_next;
279 	delete_sound_callback(scb->scb_callback);
280 	vim_free(scb);
281     }
282 }
283 # endif
284 
285 #elif defined(MSWIN)
286 
287 /*
288  * Sound implementation for MS-Windows.
289  */
290 
291 static HWND g_hWndSound = NULL;
292 
293     static LRESULT CALLBACK
294 sound_wndproc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
295 {
296     soundcb_T	*p;
297 
298     switch (message)
299     {
300 	case MM_MCINOTIFY:
301 	    for (p = first_callback; p != NULL; p = p->snd_next)
302 		if (p->snd_device_id == (MCIDEVICEID) lParam)
303 		{
304 		    typval_T	argv[3];
305 		    typval_T	rettv;
306 		    char	buf[32];
307 
308 		    vim_snprintf(buf, sizeof(buf), "close sound%06ld",
309 								p->snd_id);
310 		    mciSendString(buf, NULL, 0, 0);
311 
312 		    argv[0].v_type = VAR_NUMBER;
313 		    argv[0].vval.v_number = p->snd_id;
314 		    argv[1].v_type = VAR_NUMBER;
315 		    argv[1].vval.v_number =
316 					wParam == MCI_NOTIFY_SUCCESSFUL ? 0
317 				      : wParam == MCI_NOTIFY_ABORTED ? 1 : 2;
318 		    argv[2].v_type = VAR_UNKNOWN;
319 
320 		    call_callback(&p->snd_callback, -1, &rettv, 2, argv);
321 		    clear_tv(&rettv);
322 
323 		    delete_sound_callback(p);
324 		    redraw_after_callback(TRUE);
325 
326 		}
327 	    break;
328     }
329 
330     return DefWindowProc(hwnd, message, wParam, lParam);
331 }
332 
333     static HWND
334 sound_window()
335 {
336     if (g_hWndSound == NULL)
337     {
338 	LPCSTR clazz = "VimSound";
339 	WNDCLASS wndclass = {
340 	    0, sound_wndproc, 0, 0, g_hinst, NULL, 0, 0, NULL, clazz };
341 	RegisterClass(&wndclass);
342 	g_hWndSound = CreateWindow(clazz, NULL, 0, 0, 0, 0, 0,
343 		HWND_MESSAGE, NULL, g_hinst, NULL);
344     }
345 
346     return g_hWndSound;
347 }
348 
349     void
350 f_sound_playevent(typval_T *argvars, typval_T *rettv)
351 {
352     WCHAR	    *wp;
353 
354     wp = enc_to_utf16(tv_get_string(&argvars[0]), NULL);
355     if (wp == NULL)
356 	return;
357 
358     PlaySoundW(wp, NULL, SND_ASYNC | SND_ALIAS);
359     free(wp);
360 
361     rettv->vval.v_number = ++sound_id;
362 }
363 
364     void
365 f_sound_playfile(typval_T *argvars, typval_T *rettv)
366 {
367     long	newid = sound_id + 1;
368     size_t	len;
369     char_u	*p, *esc;
370     WCHAR	*wp;
371     soundcb_T	*soundcb;
372     char	buf[32];
373     MCIERROR	err;
374 
375     esc = vim_strsave_shellescape(tv_get_string(&argvars[0]), FALSE, FALSE);
376 
377     len = STRLEN(esc) + 5 + 18 + 1;
378     p = alloc(len);
379     if (p == NULL)
380     {
381 	free(esc);
382 	return;
383     }
384     vim_snprintf((char *)p, len, "open %s alias sound%06ld", esc, newid);
385     free(esc);
386 
387     wp = enc_to_utf16((char_u *)p, NULL);
388     free(p);
389     if (wp == NULL)
390 	return;
391 
392     err = mciSendStringW(wp, NULL, 0, sound_window());
393     free(wp);
394     if (err != 0)
395 	return;
396 
397     vim_snprintf(buf, sizeof(buf), "play sound%06ld notify", newid);
398     err = mciSendString(buf, NULL, 0, sound_window());
399     if (err != 0)
400 	goto failure;
401 
402     sound_id = newid;
403     rettv->vval.v_number = sound_id;
404 
405     soundcb = get_sound_callback(&argvars[1]);
406     if (soundcb != NULL)
407     {
408 	vim_snprintf(buf, sizeof(buf), "sound%06ld", newid);
409 	soundcb->snd_id = newid;
410 	soundcb->snd_device_id = mciGetDeviceID(buf);
411     }
412     return;
413 
414 failure:
415     vim_snprintf(buf, sizeof(buf), "close sound%06ld", newid);
416     mciSendString(buf, NULL, 0, NULL);
417 }
418 
419     void
420 f_sound_stop(typval_T *argvars, typval_T *rettv UNUSED)
421 {
422     long    id = tv_get_number(&argvars[0]);
423     char    buf[32];
424 
425     vim_snprintf(buf, sizeof(buf), "stop sound%06ld", id);
426     mciSendString(buf, NULL, 0, NULL);
427 }
428 
429     void
430 f_sound_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
431 {
432     PlaySoundW(NULL, NULL, 0);
433     mciSendString("close all", NULL, 0, NULL);
434 }
435 
436 # if defined(EXITFREE)
437     void
438 sound_free(void)
439 {
440     CloseWindow(g_hWndSound);
441 
442     while (first_callback != NULL)
443 	delete_sound_callback(first_callback);
444 }
445 # endif
446 
447 #endif // MSWIN
448 
449 #endif  // FEAT_SOUND
450