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