blob: f0f5ab0d11b51dcfe8541de8f023dee21a3de2a1 [file] [log] [blame]
Bram Moolenaar427f5b62019-06-09 13:43:51 +02001/* 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
Bram Moolenaar9b283522019-06-17 22:19:33 +020016#if defined(FEAT_SOUND) || defined(PROTO)
Bram Moolenaar427f5b62019-06-09 13:43:51 +020017
18static long sound_id = 0;
Bram Moolenaar427f5b62019-06-09 13:43:51 +020019
20typedef struct soundcb_S soundcb_T;
21
22struct soundcb_S {
23 callback_T snd_callback;
Bram Moolenaar9b283522019-06-17 22:19:33 +020024#ifdef MSWIN
25 MCIDEVICEID snd_device_id;
26 long snd_id;
27#endif
Bram Moolenaar427f5b62019-06-09 13:43:51 +020028 soundcb_T *snd_next;
29};
30
31static soundcb_T *first_callback = NULL;
32
Bram Moolenaar28e67e02019-08-15 23:05:49 +020033/*
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
38has_any_sound_callback(void)
39{
40 return first_callback != NULL;
41}
42
Bram Moolenaar427f5b62019-06-09 13:43:51 +020043 static soundcb_T *
44get_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/*
Yee Cheng Chin4314e4f2022-10-08 13:50:05 +010068 * Call "soundcb" with proper parameters.
69 */
70 void
71call_sound_callback(soundcb_T *soundcb, long snd_id, int result)
72{
73 typval_T argv[3];
74 typval_T rettv;
75
76 argv[0].v_type = VAR_NUMBER;
77 argv[0].vval.v_number = snd_id;
78 argv[1].v_type = VAR_NUMBER;
79 argv[1].vval.v_number = result;
80 argv[2].v_type = VAR_UNKNOWN;
81
82 call_callback(&soundcb->snd_callback, -1, &rettv, 2, argv);
83 clear_tv(&rettv);
84}
85
86/*
Bram Moolenaar427f5b62019-06-09 13:43:51 +020087 * Delete "soundcb" from the list of pending callbacks.
88 */
Yee Cheng Chin4314e4f2022-10-08 13:50:05 +010089 void
Bram Moolenaar427f5b62019-06-09 13:43:51 +020090delete_sound_callback(soundcb_T *soundcb)
91{
92 soundcb_T *p;
93 soundcb_T *prev = NULL;
94
95 for (p = first_callback; p != NULL; prev = p, p = p->snd_next)
96 if (p == soundcb)
97 {
98 if (prev == NULL)
99 first_callback = p->snd_next;
100 else
101 prev->snd_next = p->snd_next;
102 free_callback(&p->snd_callback);
103 vim_free(p);
104 break;
105 }
106}
107
Bram Moolenaar9b283522019-06-17 22:19:33 +0200108#if defined(HAVE_CANBERRA) || defined(PROTO)
109
110/*
Yee Cheng Chin4314e4f2022-10-08 13:50:05 +0100111 * Sound implementation for Linux/Unix using libcanberra.
Bram Moolenaar9b283522019-06-17 22:19:33 +0200112 */
113# include <canberra.h>
114
115static ca_context *context = NULL;
116
Bram Moolenaar28e67e02019-08-15 23:05:49 +0200117// Structure to store info about a sound callback to be invoked soon.
118typedef struct soundcb_queue_S soundcb_queue_T;
119
120struct soundcb_queue_S {
121 soundcb_queue_T *scb_next;
122 uint32_t scb_id; // ID of the sound
123 int scb_result; // CA_ value
124 soundcb_T *scb_callback; // function to call
125};
126
127// Queue of callbacks to invoke from the main loop.
128static soundcb_queue_T *callback_queue = NULL;
129
130/*
131 * Add a callback to the queue of callbacks to invoke later from the main loop.
132 * That is because the callback may be called from another thread and invoking
133 * another sound function may cause trouble.
134 */
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200135 static void
136sound_callback(
137 ca_context *c UNUSED,
138 uint32_t id,
139 int error_code,
140 void *userdata)
141{
Bram Moolenaar28e67e02019-08-15 23:05:49 +0200142 soundcb_T *soundcb = (soundcb_T *)userdata;
143 soundcb_queue_T *scb;
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200144
Bram Moolenaar28e67e02019-08-15 23:05:49 +0200145 scb = ALLOC_ONE(soundcb_queue_T);
146 if (scb == NULL)
147 return;
148 scb->scb_next = callback_queue;
149 callback_queue = scb;
150 scb->scb_id = id;
151 scb->scb_result = error_code == CA_SUCCESS ? 0
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200152 : error_code == CA_ERROR_CANCELED
153 || error_code == CA_ERROR_DESTROYED
154 ? 1 : 2;
Bram Moolenaar28e67e02019-08-15 23:05:49 +0200155 scb->scb_callback = soundcb;
156}
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200157
Bram Moolenaar28e67e02019-08-15 23:05:49 +0200158/*
159 * Return TRUE if there is a sound callback to be called.
160 */
161 int
162has_sound_callback_in_queue(void)
163{
164 return callback_queue != NULL;
165}
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200166
Bram Moolenaar28e67e02019-08-15 23:05:49 +0200167/*
168 * Invoke queued sound callbacks.
169 */
170 void
171invoke_sound_callback(void)
172{
173 soundcb_queue_T *scb;
Bram Moolenaar28e67e02019-08-15 23:05:49 +0200174
175 while (callback_queue != NULL)
176 {
177 scb = callback_queue;
178 callback_queue = scb->scb_next;
179
Yee Cheng Chin4314e4f2022-10-08 13:50:05 +0100180 call_sound_callback(scb->scb_callback, scb->scb_id, scb->scb_result);
Bram Moolenaar28e67e02019-08-15 23:05:49 +0200181
182 delete_sound_callback(scb->scb_callback);
Bram Moolenaar821d7712019-08-30 16:30:00 +0200183 vim_free(scb);
Bram Moolenaar28e67e02019-08-15 23:05:49 +0200184 }
Bram Moolenaare5050712021-12-09 10:51:05 +0000185 redraw_after_callback(TRUE, FALSE);
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200186}
187
188 static void
189sound_play_common(typval_T *argvars, typval_T *rettv, int playfile)
190{
Yegappan Lakshmanan5bca9062021-07-24 21:33:26 +0200191 if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
192 return;
193
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200194 if (context == NULL)
195 ca_context_create(&context);
196 if (context != NULL)
197 {
198 soundcb_T *soundcb = get_sound_callback(&argvars[1]);
199 int res = CA_ERROR_INVALID;
200
201 ++sound_id;
202 if (soundcb == NULL)
203 {
204 res = ca_context_play(context, sound_id,
205 playfile ? CA_PROP_MEDIA_FILENAME : CA_PROP_EVENT_ID,
206 tv_get_string(&argvars[0]),
207 CA_PROP_CANBERRA_CACHE_CONTROL, "volatile",
208 NULL);
209 }
210 else
211 {
212 static ca_proplist *proplist = NULL;
213
214 ca_proplist_create(&proplist);
215 if (proplist != NULL)
216 {
217 if (playfile)
218 ca_proplist_sets(proplist, CA_PROP_MEDIA_FILENAME,
219 (char *)tv_get_string(&argvars[0]));
220 else
221 ca_proplist_sets(proplist, CA_PROP_EVENT_ID,
222 (char *)tv_get_string(&argvars[0]));
223 ca_proplist_sets(proplist, CA_PROP_CANBERRA_CACHE_CONTROL,
224 "volatile");
225 res = ca_context_play_full(context, sound_id, proplist,
226 sound_callback, soundcb);
227 if (res != CA_SUCCESS)
228 delete_sound_callback(soundcb);
229
230 ca_proplist_destroy(proplist);
231 }
232 }
233 rettv->vval.v_number = res == CA_SUCCESS ? sound_id : 0;
234 }
235}
236
237 void
238f_sound_playevent(typval_T *argvars, typval_T *rettv)
239{
240 sound_play_common(argvars, rettv, FALSE);
241}
242
Bram Moolenaar3ff5f0f2019-06-10 13:11:22 +0200243/*
244 * implementation of sound_playfile({path} [, {callback}])
245 */
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200246 void
247f_sound_playfile(typval_T *argvars, typval_T *rettv)
248{
249 sound_play_common(argvars, rettv, TRUE);
250}
251
Bram Moolenaar3ff5f0f2019-06-10 13:11:22 +0200252/*
253 * implementation of sound_stop({id})
254 */
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200255 void
256f_sound_stop(typval_T *argvars, typval_T *rettv UNUSED)
257{
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +0200258 if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
259 return;
260
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200261 if (context != NULL)
262 ca_context_cancel(context, tv_get_number(&argvars[0]));
263}
264
Bram Moolenaar3ff5f0f2019-06-10 13:11:22 +0200265/*
266 * implementation of sound_clear()
267 */
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200268 void
Bram Moolenaar3ff5f0f2019-06-10 13:11:22 +0200269f_sound_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200270{
271 if (context != NULL)
272 {
273 ca_context_destroy(context);
274 context = NULL;
275 }
276}
277
Bram Moolenaar9b283522019-06-17 22:19:33 +0200278# if defined(EXITFREE) || defined(PROTO)
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200279 void
280sound_free(void)
281{
Bram Moolenaar821d7712019-08-30 16:30:00 +0200282 soundcb_queue_T *scb;
283
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200284 if (context != NULL)
285 ca_context_destroy(context);
Bram Moolenaar821d7712019-08-30 16:30:00 +0200286
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200287 while (first_callback != NULL)
288 delete_sound_callback(first_callback);
Bram Moolenaar821d7712019-08-30 16:30:00 +0200289
290 while (callback_queue != NULL)
291 {
292 scb = callback_queue;
293 callback_queue = scb->scb_next;
294 delete_sound_callback(scb->scb_callback);
295 vim_free(scb);
296 }
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200297}
Bram Moolenaar9b283522019-06-17 22:19:33 +0200298# endif
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200299
Bram Moolenaar9b283522019-06-17 22:19:33 +0200300#elif defined(MSWIN)
301
302/*
303 * Sound implementation for MS-Windows.
304 */
305
306static HWND g_hWndSound = NULL;
307
308 static LRESULT CALLBACK
309sound_wndproc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
310{
311 soundcb_T *p;
312
313 switch (message)
314 {
315 case MM_MCINOTIFY:
316 for (p = first_callback; p != NULL; p = p->snd_next)
317 if (p->snd_device_id == (MCIDEVICEID) lParam)
318 {
Bram Moolenaar9b283522019-06-17 22:19:33 +0200319 char buf[32];
320
321 vim_snprintf(buf, sizeof(buf), "close sound%06ld",
322 p->snd_id);
323 mciSendString(buf, NULL, 0, 0);
324
Yee Cheng Chin4314e4f2022-10-08 13:50:05 +0100325 long result = wParam == MCI_NOTIFY_SUCCESSFUL ? 0
326 : wParam == MCI_NOTIFY_ABORTED ? 1 : 2;
327 call_sound_callback(p, p->snd_id, result);
Bram Moolenaar9b283522019-06-17 22:19:33 +0200328
329 delete_sound_callback(p);
Bram Moolenaare5050712021-12-09 10:51:05 +0000330 redraw_after_callback(TRUE, FALSE);
Bram Moolenaar9b283522019-06-17 22:19:33 +0200331
332 }
333 break;
334 }
335
336 return DefWindowProc(hwnd, message, wParam, lParam);
337}
338
339 static HWND
340sound_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
356f_sound_playevent(typval_T *argvars, typval_T *rettv)
357{
358 WCHAR *wp;
359
Yegappan Lakshmanan5bca9062021-07-24 21:33:26 +0200360 if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
361 return;
362
Bram Moolenaar9b283522019-06-17 22:19:33 +0200363 wp = enc_to_utf16(tv_get_string(&argvars[0]), NULL);
364 if (wp == NULL)
365 return;
366
Dominique Pelle2f9c2092021-06-07 20:28:45 +0200367 if (PlaySoundW(wp, NULL, SND_ASYNC | SND_ALIAS))
368 rettv->vval.v_number = ++sound_id;
Bram Moolenaar9b283522019-06-17 22:19:33 +0200369 free(wp);
Bram Moolenaar9b283522019-06-17 22:19:33 +0200370}
371
372 void
373f_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
Yegappan Lakshmanan5bca9062021-07-24 21:33:26 +0200383 if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
384 return;
385
Bram Moolenaar9b283522019-06-17 22:19:33 +0200386 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
425failure:
426 vim_snprintf(buf, sizeof(buf), "close sound%06ld", newid);
427 mciSendString(buf, NULL, 0, NULL);
428}
429
430 void
431f_sound_stop(typval_T *argvars, typval_T *rettv UNUSED)
432{
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +0200433 long id;
Bram Moolenaar9b283522019-06-17 22:19:33 +0200434 char buf[32];
435
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +0200436 if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
437 return;
438
439 id = tv_get_number(&argvars[0]);
Bram Moolenaar9b283522019-06-17 22:19:33 +0200440 vim_snprintf(buf, sizeof(buf), "stop sound%06ld", id);
441 mciSendString(buf, NULL, 0, NULL);
442}
443
444 void
445f_sound_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
446{
Bram Moolenaar1b33bee2019-09-07 14:50:49 +0200447 PlaySoundW(NULL, NULL, 0);
Bram Moolenaar9b283522019-06-17 22:19:33 +0200448 mciSendString("close all", NULL, 0, NULL);
449}
450
451# if defined(EXITFREE)
452 void
453sound_free(void)
454{
455 CloseWindow(g_hWndSound);
456
457 while (first_callback != NULL)
458 delete_sound_callback(first_callback);
459}
460# endif
461
Yee Cheng Chin4314e4f2022-10-08 13:50:05 +0100462#elif defined(MACOS_X_DARWIN)
463
464// Sound implementation for macOS.
465 static void
466sound_play_common(typval_T *argvars, typval_T *rettv, bool playfile)
467{
468 if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
469 return;
470
471 char_u *sound_name = tv_get_string(&argvars[0]);
472 soundcb_T *soundcb = get_sound_callback(&argvars[1]);
473
474 ++sound_id;
475
476 bool play_success = sound_mch_play(sound_name, sound_id, soundcb, playfile);
477 if (!play_success && soundcb)
478 {
479 delete_sound_callback(soundcb);
480 }
481 rettv->vval.v_number = play_success ? sound_id : 0;
482}
483
484 void
485f_sound_playevent(typval_T *argvars, typval_T *rettv)
486{
487 sound_play_common(argvars, rettv, false);
488}
489
490 void
491f_sound_playfile(typval_T *argvars, typval_T *rettv)
492{
493 sound_play_common(argvars, rettv, true);
494}
495
496 void
497f_sound_stop(typval_T *argvars, typval_T *rettv UNUSED)
498{
499 if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
500 return;
501 sound_mch_stop(tv_get_number(&argvars[0]));
502}
503
504 void
505f_sound_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
506{
507 sound_mch_clear();
508}
509
510#if defined(EXITFREE) || defined(PROTO)
511 void
512sound_free(void)
513{
514 sound_mch_free();
515 while (first_callback != NULL)
516 delete_sound_callback(first_callback);
517}
518#endif
519
520#endif // MACOS_X_DARWIN
Bram Moolenaar9b283522019-06-17 22:19:33 +0200521
522#endif // FEAT_SOUND