blob: edb606a2d04edfb515d9ec11da9c27e278e7c4e6 [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
Bram Moolenaard505c822022-10-19 19:24:48 +010020// soundcb_T is typdef'ed in proto/sound.pro
Bram Moolenaar427f5b62019-06-09 13:43:51 +020021
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);
Bram Moolenaarc0370522022-12-03 13:52:24 +000063 if (callback.cb_free_name)
64 vim_free(callback.cb_name);
Bram Moolenaar427f5b62019-06-09 13:43:51 +020065 }
66 return soundcb;
67}
68
69/*
Yee Cheng Chin4314e4f2022-10-08 13:50:05 +010070 * Call "soundcb" with proper parameters.
71 */
72 void
73call_sound_callback(soundcb_T *soundcb, long snd_id, int result)
74{
75 typval_T argv[3];
76 typval_T rettv;
77
78 argv[0].v_type = VAR_NUMBER;
79 argv[0].vval.v_number = snd_id;
80 argv[1].v_type = VAR_NUMBER;
81 argv[1].vval.v_number = result;
82 argv[2].v_type = VAR_UNKNOWN;
83
84 call_callback(&soundcb->snd_callback, -1, &rettv, 2, argv);
85 clear_tv(&rettv);
86}
87
88/*
Bram Moolenaar427f5b62019-06-09 13:43:51 +020089 * Delete "soundcb" from the list of pending callbacks.
90 */
Yee Cheng Chin4314e4f2022-10-08 13:50:05 +010091 void
Bram Moolenaar427f5b62019-06-09 13:43:51 +020092delete_sound_callback(soundcb_T *soundcb)
93{
94 soundcb_T *p;
95 soundcb_T *prev = NULL;
96
97 for (p = first_callback; p != NULL; prev = p, p = p->snd_next)
98 if (p == soundcb)
99 {
100 if (prev == NULL)
101 first_callback = p->snd_next;
102 else
103 prev->snd_next = p->snd_next;
104 free_callback(&p->snd_callback);
105 vim_free(p);
106 break;
107 }
108}
109
Bram Moolenaar9b283522019-06-17 22:19:33 +0200110#if defined(HAVE_CANBERRA) || defined(PROTO)
111
112/*
Yee Cheng Chin4314e4f2022-10-08 13:50:05 +0100113 * Sound implementation for Linux/Unix using libcanberra.
Bram Moolenaar9b283522019-06-17 22:19:33 +0200114 */
115# include <canberra.h>
116
117static ca_context *context = NULL;
118
Bram Moolenaar28e67e02019-08-15 23:05:49 +0200119// Structure to store info about a sound callback to be invoked soon.
120typedef struct soundcb_queue_S soundcb_queue_T;
121
122struct soundcb_queue_S {
123 soundcb_queue_T *scb_next;
124 uint32_t scb_id; // ID of the sound
125 int scb_result; // CA_ value
126 soundcb_T *scb_callback; // function to call
127};
128
129// Queue of callbacks to invoke from the main loop.
130static soundcb_queue_T *callback_queue = NULL;
131
132/*
133 * Add a callback to the queue of callbacks to invoke later from the main loop.
134 * That is because the callback may be called from another thread and invoking
135 * another sound function may cause trouble.
136 */
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200137 static void
138sound_callback(
139 ca_context *c UNUSED,
140 uint32_t id,
141 int error_code,
142 void *userdata)
143{
Bram Moolenaar28e67e02019-08-15 23:05:49 +0200144 soundcb_T *soundcb = (soundcb_T *)userdata;
145 soundcb_queue_T *scb;
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200146
Bram Moolenaar28e67e02019-08-15 23:05:49 +0200147 scb = ALLOC_ONE(soundcb_queue_T);
148 if (scb == NULL)
149 return;
150 scb->scb_next = callback_queue;
151 callback_queue = scb;
152 scb->scb_id = id;
153 scb->scb_result = error_code == CA_SUCCESS ? 0
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200154 : error_code == CA_ERROR_CANCELED
155 || error_code == CA_ERROR_DESTROYED
156 ? 1 : 2;
Bram Moolenaar28e67e02019-08-15 23:05:49 +0200157 scb->scb_callback = soundcb;
158}
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200159
Bram Moolenaar28e67e02019-08-15 23:05:49 +0200160/*
161 * Return TRUE if there is a sound callback to be called.
162 */
163 int
164has_sound_callback_in_queue(void)
165{
166 return callback_queue != NULL;
167}
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200168
Bram Moolenaar28e67e02019-08-15 23:05:49 +0200169/*
170 * Invoke queued sound callbacks.
171 */
172 void
173invoke_sound_callback(void)
174{
175 soundcb_queue_T *scb;
Bram Moolenaar28e67e02019-08-15 23:05:49 +0200176
177 while (callback_queue != NULL)
178 {
179 scb = callback_queue;
180 callback_queue = scb->scb_next;
181
Yee Cheng Chin4314e4f2022-10-08 13:50:05 +0100182 call_sound_callback(scb->scb_callback, scb->scb_id, scb->scb_result);
Bram Moolenaar28e67e02019-08-15 23:05:49 +0200183
184 delete_sound_callback(scb->scb_callback);
Bram Moolenaar821d7712019-08-30 16:30:00 +0200185 vim_free(scb);
Bram Moolenaar28e67e02019-08-15 23:05:49 +0200186 }
Bram Moolenaare5050712021-12-09 10:51:05 +0000187 redraw_after_callback(TRUE, FALSE);
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200188}
189
190 static void
191sound_play_common(typval_T *argvars, typval_T *rettv, int playfile)
192{
Yegappan Lakshmanan5bca9062021-07-24 21:33:26 +0200193 if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
194 return;
195
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200196 if (context == NULL)
197 ca_context_create(&context);
198 if (context != NULL)
199 {
200 soundcb_T *soundcb = get_sound_callback(&argvars[1]);
201 int res = CA_ERROR_INVALID;
202
203 ++sound_id;
204 if (soundcb == NULL)
205 {
206 res = ca_context_play(context, sound_id,
207 playfile ? CA_PROP_MEDIA_FILENAME : CA_PROP_EVENT_ID,
208 tv_get_string(&argvars[0]),
209 CA_PROP_CANBERRA_CACHE_CONTROL, "volatile",
210 NULL);
211 }
212 else
213 {
214 static ca_proplist *proplist = NULL;
215
216 ca_proplist_create(&proplist);
217 if (proplist != NULL)
218 {
219 if (playfile)
220 ca_proplist_sets(proplist, CA_PROP_MEDIA_FILENAME,
221 (char *)tv_get_string(&argvars[0]));
222 else
223 ca_proplist_sets(proplist, CA_PROP_EVENT_ID,
224 (char *)tv_get_string(&argvars[0]));
225 ca_proplist_sets(proplist, CA_PROP_CANBERRA_CACHE_CONTROL,
226 "volatile");
227 res = ca_context_play_full(context, sound_id, proplist,
228 sound_callback, soundcb);
229 if (res != CA_SUCCESS)
230 delete_sound_callback(soundcb);
231
232 ca_proplist_destroy(proplist);
233 }
234 }
235 rettv->vval.v_number = res == CA_SUCCESS ? sound_id : 0;
236 }
237}
238
239 void
240f_sound_playevent(typval_T *argvars, typval_T *rettv)
241{
242 sound_play_common(argvars, rettv, FALSE);
243}
244
Bram Moolenaar3ff5f0f2019-06-10 13:11:22 +0200245/*
246 * implementation of sound_playfile({path} [, {callback}])
247 */
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200248 void
249f_sound_playfile(typval_T *argvars, typval_T *rettv)
250{
251 sound_play_common(argvars, rettv, TRUE);
252}
253
Bram Moolenaar3ff5f0f2019-06-10 13:11:22 +0200254/*
255 * implementation of sound_stop({id})
256 */
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200257 void
258f_sound_stop(typval_T *argvars, typval_T *rettv UNUSED)
259{
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +0200260 if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
261 return;
262
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200263 if (context != NULL)
264 ca_context_cancel(context, tv_get_number(&argvars[0]));
265}
266
Bram Moolenaar3ff5f0f2019-06-10 13:11:22 +0200267/*
268 * implementation of sound_clear()
269 */
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200270 void
Bram Moolenaar3ff5f0f2019-06-10 13:11:22 +0200271f_sound_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200272{
273 if (context != NULL)
274 {
275 ca_context_destroy(context);
276 context = NULL;
277 }
278}
279
Bram Moolenaar9b283522019-06-17 22:19:33 +0200280# if defined(EXITFREE) || defined(PROTO)
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200281 void
282sound_free(void)
283{
Bram Moolenaar821d7712019-08-30 16:30:00 +0200284 soundcb_queue_T *scb;
285
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200286 if (context != NULL)
287 ca_context_destroy(context);
Bram Moolenaar821d7712019-08-30 16:30:00 +0200288
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200289 while (first_callback != NULL)
290 delete_sound_callback(first_callback);
Bram Moolenaar821d7712019-08-30 16:30:00 +0200291
292 while (callback_queue != NULL)
293 {
294 scb = callback_queue;
295 callback_queue = scb->scb_next;
296 delete_sound_callback(scb->scb_callback);
297 vim_free(scb);
298 }
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200299}
Bram Moolenaar9b283522019-06-17 22:19:33 +0200300# endif
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200301
Bram Moolenaar9b283522019-06-17 22:19:33 +0200302#elif defined(MSWIN)
303
304/*
305 * Sound implementation for MS-Windows.
306 */
307
308static HWND g_hWndSound = NULL;
309
310 static LRESULT CALLBACK
311sound_wndproc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
312{
313 soundcb_T *p;
314
315 switch (message)
316 {
317 case MM_MCINOTIFY:
318 for (p = first_callback; p != NULL; p = p->snd_next)
319 if (p->snd_device_id == (MCIDEVICEID) lParam)
320 {
Bram Moolenaar9b283522019-06-17 22:19:33 +0200321 char buf[32];
322
323 vim_snprintf(buf, sizeof(buf), "close sound%06ld",
324 p->snd_id);
325 mciSendString(buf, NULL, 0, 0);
326
Yee Cheng Chin4314e4f2022-10-08 13:50:05 +0100327 long result = wParam == MCI_NOTIFY_SUCCESSFUL ? 0
328 : wParam == MCI_NOTIFY_ABORTED ? 1 : 2;
329 call_sound_callback(p, p->snd_id, result);
Bram Moolenaar9b283522019-06-17 22:19:33 +0200330
331 delete_sound_callback(p);
Bram Moolenaare5050712021-12-09 10:51:05 +0000332 redraw_after_callback(TRUE, FALSE);
Bram Moolenaar9b283522019-06-17 22:19:33 +0200333
334 }
335 break;
336 }
337
338 return DefWindowProc(hwnd, message, wParam, lParam);
339}
340
341 static HWND
342sound_window()
343{
344 if (g_hWndSound == NULL)
345 {
346 LPCSTR clazz = "VimSound";
347 WNDCLASS wndclass = {
348 0, sound_wndproc, 0, 0, g_hinst, NULL, 0, 0, NULL, clazz };
349 RegisterClass(&wndclass);
350 g_hWndSound = CreateWindow(clazz, NULL, 0, 0, 0, 0, 0,
351 HWND_MESSAGE, NULL, g_hinst, NULL);
352 }
353
354 return g_hWndSound;
355}
356
357 void
358f_sound_playevent(typval_T *argvars, typval_T *rettv)
359{
360 WCHAR *wp;
361
Yegappan Lakshmanan5bca9062021-07-24 21:33:26 +0200362 if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
363 return;
364
Bram Moolenaar9b283522019-06-17 22:19:33 +0200365 wp = enc_to_utf16(tv_get_string(&argvars[0]), NULL);
366 if (wp == NULL)
367 return;
368
Dominique Pelle2f9c2092021-06-07 20:28:45 +0200369 if (PlaySoundW(wp, NULL, SND_ASYNC | SND_ALIAS))
370 rettv->vval.v_number = ++sound_id;
Bram Moolenaar9b283522019-06-17 22:19:33 +0200371 free(wp);
Bram Moolenaar9b283522019-06-17 22:19:33 +0200372}
373
374 void
375f_sound_playfile(typval_T *argvars, typval_T *rettv)
376{
377 long newid = sound_id + 1;
378 size_t len;
379 char_u *p, *esc;
380 WCHAR *wp;
381 soundcb_T *soundcb;
382 char buf[32];
383 MCIERROR err;
384
Yegappan Lakshmanan5bca9062021-07-24 21:33:26 +0200385 if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
386 return;
387
Bram Moolenaar9b283522019-06-17 22:19:33 +0200388 esc = vim_strsave_shellescape(tv_get_string(&argvars[0]), FALSE, FALSE);
389
390 len = STRLEN(esc) + 5 + 18 + 1;
391 p = alloc(len);
392 if (p == NULL)
393 {
394 free(esc);
395 return;
396 }
397 vim_snprintf((char *)p, len, "open %s alias sound%06ld", esc, newid);
398 free(esc);
399
400 wp = enc_to_utf16((char_u *)p, NULL);
401 free(p);
402 if (wp == NULL)
403 return;
404
405 err = mciSendStringW(wp, NULL, 0, sound_window());
406 free(wp);
407 if (err != 0)
408 return;
409
410 vim_snprintf(buf, sizeof(buf), "play sound%06ld notify", newid);
411 err = mciSendString(buf, NULL, 0, sound_window());
412 if (err != 0)
413 goto failure;
414
415 sound_id = newid;
416 rettv->vval.v_number = sound_id;
417
418 soundcb = get_sound_callback(&argvars[1]);
419 if (soundcb != NULL)
420 {
421 vim_snprintf(buf, sizeof(buf), "sound%06ld", newid);
422 soundcb->snd_id = newid;
423 soundcb->snd_device_id = mciGetDeviceID(buf);
424 }
425 return;
426
427failure:
428 vim_snprintf(buf, sizeof(buf), "close sound%06ld", newid);
429 mciSendString(buf, NULL, 0, NULL);
430}
431
432 void
433f_sound_stop(typval_T *argvars, typval_T *rettv UNUSED)
434{
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +0200435 long id;
Bram Moolenaar9b283522019-06-17 22:19:33 +0200436 char buf[32];
437
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +0200438 if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
439 return;
440
441 id = tv_get_number(&argvars[0]);
Bram Moolenaar9b283522019-06-17 22:19:33 +0200442 vim_snprintf(buf, sizeof(buf), "stop sound%06ld", id);
443 mciSendString(buf, NULL, 0, NULL);
444}
445
446 void
447f_sound_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
448{
Bram Moolenaar1b33bee2019-09-07 14:50:49 +0200449 PlaySoundW(NULL, NULL, 0);
Bram Moolenaar9b283522019-06-17 22:19:33 +0200450 mciSendString("close all", NULL, 0, NULL);
451}
452
453# if defined(EXITFREE)
454 void
455sound_free(void)
456{
457 CloseWindow(g_hWndSound);
458
459 while (first_callback != NULL)
460 delete_sound_callback(first_callback);
461}
462# endif
463
Yee Cheng Chin4314e4f2022-10-08 13:50:05 +0100464#elif defined(MACOS_X_DARWIN)
465
466// Sound implementation for macOS.
467 static void
468sound_play_common(typval_T *argvars, typval_T *rettv, bool playfile)
469{
470 if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
471 return;
472
473 char_u *sound_name = tv_get_string(&argvars[0]);
474 soundcb_T *soundcb = get_sound_callback(&argvars[1]);
475
476 ++sound_id;
477
478 bool play_success = sound_mch_play(sound_name, sound_id, soundcb, playfile);
479 if (!play_success && soundcb)
480 {
481 delete_sound_callback(soundcb);
482 }
483 rettv->vval.v_number = play_success ? sound_id : 0;
484}
485
486 void
487f_sound_playevent(typval_T *argvars, typval_T *rettv)
488{
489 sound_play_common(argvars, rettv, false);
490}
491
492 void
493f_sound_playfile(typval_T *argvars, typval_T *rettv)
494{
495 sound_play_common(argvars, rettv, true);
496}
497
498 void
499f_sound_stop(typval_T *argvars, typval_T *rettv UNUSED)
500{
501 if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
502 return;
503 sound_mch_stop(tv_get_number(&argvars[0]));
504}
505
506 void
507f_sound_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
508{
509 sound_mch_clear();
510}
511
512#if defined(EXITFREE) || defined(PROTO)
513 void
514sound_free(void)
515{
516 sound_mch_free();
517 while (first_callback != NULL)
518 delete_sound_callback(first_callback);
519}
520#endif
521
522#endif // MACOS_X_DARWIN
Bram Moolenaar9b283522019-06-17 22:19:33 +0200523
524#endif // FEAT_SOUND