blob: 9d91f6c086d003b7419889f50722f3c1ecad4a6a [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/*
68 * Delete "soundcb" from the list of pending callbacks.
69 */
70 static void
71delete_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
Bram Moolenaar9b283522019-06-17 22:19:33 +020089#if defined(HAVE_CANBERRA) || defined(PROTO)
90
91/*
92 * Sound implementation for Linux/Unix/Mac using libcanberra.
93 */
94# include <canberra.h>
95
96static ca_context *context = NULL;
97
Bram Moolenaar28e67e02019-08-15 23:05:49 +020098// Structure to store info about a sound callback to be invoked soon.
99typedef struct soundcb_queue_S soundcb_queue_T;
100
101struct 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.
109static 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 */
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200116 static void
117sound_callback(
118 ca_context *c UNUSED,
119 uint32_t id,
120 int error_code,
121 void *userdata)
122{
Bram Moolenaar28e67e02019-08-15 23:05:49 +0200123 soundcb_T *soundcb = (soundcb_T *)userdata;
124 soundcb_queue_T *scb;
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200125
Bram Moolenaar28e67e02019-08-15 23:05:49 +0200126 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
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200133 : error_code == CA_ERROR_CANCELED
134 || error_code == CA_ERROR_DESTROYED
135 ? 1 : 2;
Bram Moolenaar28e67e02019-08-15 23:05:49 +0200136 scb->scb_callback = soundcb;
137}
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200138
Bram Moolenaar28e67e02019-08-15 23:05:49 +0200139/*
140 * Return TRUE if there is a sound callback to be called.
141 */
142 int
143has_sound_callback_in_queue(void)
144{
145 return callback_queue != NULL;
146}
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200147
Bram Moolenaar28e67e02019-08-15 23:05:49 +0200148/*
149 * Invoke queued sound callbacks.
150 */
151 void
152invoke_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);
Bram Moolenaar821d7712019-08-30 16:30:00 +0200174 vim_free(scb);
Bram Moolenaar28e67e02019-08-15 23:05:49 +0200175 }
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200176 redraw_after_callback(TRUE);
177}
178
179 static void
180sound_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
226f_sound_playevent(typval_T *argvars, typval_T *rettv)
227{
228 sound_play_common(argvars, rettv, FALSE);
229}
230
Bram Moolenaar3ff5f0f2019-06-10 13:11:22 +0200231/*
232 * implementation of sound_playfile({path} [, {callback}])
233 */
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200234 void
235f_sound_playfile(typval_T *argvars, typval_T *rettv)
236{
237 sound_play_common(argvars, rettv, TRUE);
238}
239
Bram Moolenaar3ff5f0f2019-06-10 13:11:22 +0200240/*
241 * implementation of sound_stop({id})
242 */
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200243 void
244f_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
Bram Moolenaar3ff5f0f2019-06-10 13:11:22 +0200250/*
251 * implementation of sound_clear()
252 */
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200253 void
Bram Moolenaar3ff5f0f2019-06-10 13:11:22 +0200254f_sound_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200255{
256 if (context != NULL)
257 {
258 ca_context_destroy(context);
259 context = NULL;
260 }
261}
262
Bram Moolenaar9b283522019-06-17 22:19:33 +0200263# if defined(EXITFREE) || defined(PROTO)
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200264 void
265sound_free(void)
266{
Bram Moolenaar821d7712019-08-30 16:30:00 +0200267 soundcb_queue_T *scb;
268
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200269 if (context != NULL)
270 ca_context_destroy(context);
Bram Moolenaar821d7712019-08-30 16:30:00 +0200271
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200272 while (first_callback != NULL)
273 delete_sound_callback(first_callback);
Bram Moolenaar821d7712019-08-30 16:30:00 +0200274
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 }
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200282}
Bram Moolenaar9b283522019-06-17 22:19:33 +0200283# endif
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200284
Bram Moolenaar9b283522019-06-17 22:19:33 +0200285#elif defined(MSWIN)
286
287/*
288 * Sound implementation for MS-Windows.
289 */
290
291static HWND g_hWndSound = NULL;
292
293 static LRESULT CALLBACK
294sound_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;
Bram Moolenaar9b283522019-06-17 22:19:33 +0200306 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
Bram Moolenaarb2129062019-08-03 18:31:11 +0200320 call_callback(&p->snd_callback, -1, &rettv, 2, argv);
Bram Moolenaar9b283522019-06-17 22:19:33 +0200321 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
334sound_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
350f_sound_playevent(typval_T *argvars, typval_T *rettv)
351{
352 WCHAR *wp;
353
Bram Moolenaar9b283522019-06-17 22:19:33 +0200354 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
365f_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
Bram Moolenaar9b283522019-06-17 22:19:33 +0200375 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
414failure:
415 vim_snprintf(buf, sizeof(buf), "close sound%06ld", newid);
416 mciSendString(buf, NULL, 0, NULL);
417}
418
419 void
420f_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
430f_sound_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
431{
Bram Moolenaar1b33bee2019-09-07 14:50:49 +0200432 PlaySoundW(NULL, NULL, 0);
Bram Moolenaar9b283522019-06-17 22:19:33 +0200433 mciSendString("close all", NULL, 0, NULL);
434}
435
436# if defined(EXITFREE)
437 void
438sound_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