blob: c104b0d9a4899c74662f01e76714767b27a3de08 [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
33 static soundcb_T *
34get_sound_callback(typval_T *arg)
35{
36 callback_T callback;
37 soundcb_T *soundcb;
38
39 if (arg->v_type == VAR_UNKNOWN)
40 return NULL;
41 callback = get_callback(arg);
42 if (callback.cb_name == NULL)
43 return NULL;
44
45 soundcb = ALLOC_ONE(soundcb_T);
46 if (soundcb == NULL)
47 free_callback(&callback);
48 else
49 {
50 soundcb->snd_next = first_callback;
51 first_callback = soundcb;
52 set_callback(&soundcb->snd_callback, &callback);
53 }
54 return soundcb;
55}
56
57/*
58 * Delete "soundcb" from the list of pending callbacks.
59 */
60 static void
61delete_sound_callback(soundcb_T *soundcb)
62{
63 soundcb_T *p;
64 soundcb_T *prev = NULL;
65
66 for (p = first_callback; p != NULL; prev = p, p = p->snd_next)
67 if (p == soundcb)
68 {
69 if (prev == NULL)
70 first_callback = p->snd_next;
71 else
72 prev->snd_next = p->snd_next;
73 free_callback(&p->snd_callback);
74 vim_free(p);
75 break;
76 }
77}
78
Bram Moolenaar9b283522019-06-17 22:19:33 +020079#if defined(HAVE_CANBERRA) || defined(PROTO)
80
81/*
82 * Sound implementation for Linux/Unix/Mac using libcanberra.
83 */
84# include <canberra.h>
85
86static ca_context *context = NULL;
87
Bram Moolenaar427f5b62019-06-09 13:43:51 +020088 static void
89sound_callback(
90 ca_context *c UNUSED,
91 uint32_t id,
92 int error_code,
93 void *userdata)
94{
95 soundcb_T *soundcb = (soundcb_T *)userdata;
96 typval_T argv[3];
97 typval_T rettv;
Bram Moolenaar427f5b62019-06-09 13:43:51 +020098
99 argv[0].v_type = VAR_NUMBER;
100 argv[0].vval.v_number = id;
101 argv[1].v_type = VAR_NUMBER;
102 argv[1].vval.v_number = error_code == CA_SUCCESS ? 0
103 : error_code == CA_ERROR_CANCELED
104 || error_code == CA_ERROR_DESTROYED
105 ? 1 : 2;
106 argv[2].v_type = VAR_UNKNOWN;
107
Bram Moolenaarb2129062019-08-03 18:31:11 +0200108 call_callback(&soundcb->snd_callback, -1, &rettv, 2, argv);
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200109 clear_tv(&rettv);
110
111 delete_sound_callback(soundcb);
112 redraw_after_callback(TRUE);
113}
114
115 static void
116sound_play_common(typval_T *argvars, typval_T *rettv, int playfile)
117{
118 if (context == NULL)
119 ca_context_create(&context);
120 if (context != NULL)
121 {
122 soundcb_T *soundcb = get_sound_callback(&argvars[1]);
123 int res = CA_ERROR_INVALID;
124
125 ++sound_id;
126 if (soundcb == NULL)
127 {
128 res = ca_context_play(context, sound_id,
129 playfile ? CA_PROP_MEDIA_FILENAME : CA_PROP_EVENT_ID,
130 tv_get_string(&argvars[0]),
131 CA_PROP_CANBERRA_CACHE_CONTROL, "volatile",
132 NULL);
133 }
134 else
135 {
136 static ca_proplist *proplist = NULL;
137
138 ca_proplist_create(&proplist);
139 if (proplist != NULL)
140 {
141 if (playfile)
142 ca_proplist_sets(proplist, CA_PROP_MEDIA_FILENAME,
143 (char *)tv_get_string(&argvars[0]));
144 else
145 ca_proplist_sets(proplist, CA_PROP_EVENT_ID,
146 (char *)tv_get_string(&argvars[0]));
147 ca_proplist_sets(proplist, CA_PROP_CANBERRA_CACHE_CONTROL,
148 "volatile");
149 res = ca_context_play_full(context, sound_id, proplist,
150 sound_callback, soundcb);
151 if (res != CA_SUCCESS)
152 delete_sound_callback(soundcb);
153
154 ca_proplist_destroy(proplist);
155 }
156 }
157 rettv->vval.v_number = res == CA_SUCCESS ? sound_id : 0;
158 }
159}
160
161 void
162f_sound_playevent(typval_T *argvars, typval_T *rettv)
163{
164 sound_play_common(argvars, rettv, FALSE);
165}
166
Bram Moolenaar3ff5f0f2019-06-10 13:11:22 +0200167/*
168 * implementation of sound_playfile({path} [, {callback}])
169 */
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200170 void
171f_sound_playfile(typval_T *argvars, typval_T *rettv)
172{
173 sound_play_common(argvars, rettv, TRUE);
174}
175
Bram Moolenaar3ff5f0f2019-06-10 13:11:22 +0200176/*
177 * implementation of sound_stop({id})
178 */
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200179 void
180f_sound_stop(typval_T *argvars, typval_T *rettv UNUSED)
181{
182 if (context != NULL)
183 ca_context_cancel(context, tv_get_number(&argvars[0]));
184}
185
Bram Moolenaar3ff5f0f2019-06-10 13:11:22 +0200186/*
187 * implementation of sound_clear()
188 */
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200189 void
Bram Moolenaar3ff5f0f2019-06-10 13:11:22 +0200190f_sound_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200191{
192 if (context != NULL)
193 {
194 ca_context_destroy(context);
195 context = NULL;
196 }
197}
198
Bram Moolenaar9b283522019-06-17 22:19:33 +0200199# if defined(EXITFREE) || defined(PROTO)
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200200 void
201sound_free(void)
202{
203 if (context != NULL)
204 ca_context_destroy(context);
205 while (first_callback != NULL)
206 delete_sound_callback(first_callback);
207}
Bram Moolenaar9b283522019-06-17 22:19:33 +0200208# endif
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200209
Bram Moolenaar9b283522019-06-17 22:19:33 +0200210#elif defined(MSWIN)
211
212/*
213 * Sound implementation for MS-Windows.
214 */
215
216static HWND g_hWndSound = NULL;
217
218 static LRESULT CALLBACK
219sound_wndproc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
220{
221 soundcb_T *p;
222
223 switch (message)
224 {
225 case MM_MCINOTIFY:
226 for (p = first_callback; p != NULL; p = p->snd_next)
227 if (p->snd_device_id == (MCIDEVICEID) lParam)
228 {
229 typval_T argv[3];
230 typval_T rettv;
231 int dummy;
232 char buf[32];
233
234 vim_snprintf(buf, sizeof(buf), "close sound%06ld",
235 p->snd_id);
236 mciSendString(buf, NULL, 0, 0);
237
238 argv[0].v_type = VAR_NUMBER;
239 argv[0].vval.v_number = p->snd_id;
240 argv[1].v_type = VAR_NUMBER;
241 argv[1].vval.v_number =
242 wParam == MCI_NOTIFY_SUCCESSFUL ? 0
243 : wParam == MCI_NOTIFY_ABORTED ? 1 : 2;
244 argv[2].v_type = VAR_UNKNOWN;
245
Bram Moolenaarb2129062019-08-03 18:31:11 +0200246 call_callback(&p->snd_callback, -1, &rettv, 2, argv);
Bram Moolenaar9b283522019-06-17 22:19:33 +0200247 clear_tv(&rettv);
248
249 delete_sound_callback(p);
250 redraw_after_callback(TRUE);
251
252 }
253 break;
254 }
255
256 return DefWindowProc(hwnd, message, wParam, lParam);
257}
258
259 static HWND
260sound_window()
261{
262 if (g_hWndSound == NULL)
263 {
264 LPCSTR clazz = "VimSound";
265 WNDCLASS wndclass = {
266 0, sound_wndproc, 0, 0, g_hinst, NULL, 0, 0, NULL, clazz };
267 RegisterClass(&wndclass);
268 g_hWndSound = CreateWindow(clazz, NULL, 0, 0, 0, 0, 0,
269 HWND_MESSAGE, NULL, g_hinst, NULL);
270 }
271
272 return g_hWndSound;
273}
274
275 void
276f_sound_playevent(typval_T *argvars, typval_T *rettv)
277{
278 WCHAR *wp;
279
280 rettv->v_type = VAR_NUMBER;
281 rettv->vval.v_number = 0;
282
283 wp = enc_to_utf16(tv_get_string(&argvars[0]), NULL);
284 if (wp == NULL)
285 return;
286
287 PlaySoundW(wp, NULL, SND_ASYNC | SND_ALIAS);
288 free(wp);
289
290 rettv->vval.v_number = ++sound_id;
291}
292
293 void
294f_sound_playfile(typval_T *argvars, typval_T *rettv)
295{
296 long newid = sound_id + 1;
297 size_t len;
298 char_u *p, *esc;
299 WCHAR *wp;
300 soundcb_T *soundcb;
301 char buf[32];
302 MCIERROR err;
303
304 rettv->v_type = VAR_NUMBER;
305 rettv->vval.v_number = 0;
306
307 esc = vim_strsave_shellescape(tv_get_string(&argvars[0]), FALSE, FALSE);
308
309 len = STRLEN(esc) + 5 + 18 + 1;
310 p = alloc(len);
311 if (p == NULL)
312 {
313 free(esc);
314 return;
315 }
316 vim_snprintf((char *)p, len, "open %s alias sound%06ld", esc, newid);
317 free(esc);
318
319 wp = enc_to_utf16((char_u *)p, NULL);
320 free(p);
321 if (wp == NULL)
322 return;
323
324 err = mciSendStringW(wp, NULL, 0, sound_window());
325 free(wp);
326 if (err != 0)
327 return;
328
329 vim_snprintf(buf, sizeof(buf), "play sound%06ld notify", newid);
330 err = mciSendString(buf, NULL, 0, sound_window());
331 if (err != 0)
332 goto failure;
333
334 sound_id = newid;
335 rettv->vval.v_number = sound_id;
336
337 soundcb = get_sound_callback(&argvars[1]);
338 if (soundcb != NULL)
339 {
340 vim_snprintf(buf, sizeof(buf), "sound%06ld", newid);
341 soundcb->snd_id = newid;
342 soundcb->snd_device_id = mciGetDeviceID(buf);
343 }
344 return;
345
346failure:
347 vim_snprintf(buf, sizeof(buf), "close sound%06ld", newid);
348 mciSendString(buf, NULL, 0, NULL);
349}
350
351 void
352f_sound_stop(typval_T *argvars, typval_T *rettv UNUSED)
353{
354 long id = tv_get_number(&argvars[0]);
355 char buf[32];
356
357 vim_snprintf(buf, sizeof(buf), "stop sound%06ld", id);
358 mciSendString(buf, NULL, 0, NULL);
359}
360
361 void
362f_sound_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
363{
364 PlaySound(NULL, NULL, 0);
365 mciSendString("close all", NULL, 0, NULL);
366}
367
368# if defined(EXITFREE)
369 void
370sound_free(void)
371{
372 CloseWindow(g_hWndSound);
373
374 while (first_callback != NULL)
375 delete_sound_callback(first_callback);
376}
377# endif
378
379#endif // MSWIN
380
381#endif // FEAT_SOUND