blob: 70f4425e928c1cf63805155090c2c01e7cc4030d [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;
98 int dummy;
99
100 argv[0].v_type = VAR_NUMBER;
101 argv[0].vval.v_number = id;
102 argv[1].v_type = VAR_NUMBER;
103 argv[1].vval.v_number = error_code == CA_SUCCESS ? 0
104 : error_code == CA_ERROR_CANCELED
105 || error_code == CA_ERROR_DESTROYED
106 ? 1 : 2;
107 argv[2].v_type = VAR_UNKNOWN;
108
109 call_callback(&soundcb->snd_callback, -1,
110 &rettv, 2, argv, NULL, 0L, 0L, &dummy, TRUE, NULL);
111 clear_tv(&rettv);
112
113 delete_sound_callback(soundcb);
114 redraw_after_callback(TRUE);
115}
116
117 static void
118sound_play_common(typval_T *argvars, typval_T *rettv, int playfile)
119{
120 if (context == NULL)
121 ca_context_create(&context);
122 if (context != NULL)
123 {
124 soundcb_T *soundcb = get_sound_callback(&argvars[1]);
125 int res = CA_ERROR_INVALID;
126
127 ++sound_id;
128 if (soundcb == NULL)
129 {
130 res = ca_context_play(context, sound_id,
131 playfile ? CA_PROP_MEDIA_FILENAME : CA_PROP_EVENT_ID,
132 tv_get_string(&argvars[0]),
133 CA_PROP_CANBERRA_CACHE_CONTROL, "volatile",
134 NULL);
135 }
136 else
137 {
138 static ca_proplist *proplist = NULL;
139
140 ca_proplist_create(&proplist);
141 if (proplist != NULL)
142 {
143 if (playfile)
144 ca_proplist_sets(proplist, CA_PROP_MEDIA_FILENAME,
145 (char *)tv_get_string(&argvars[0]));
146 else
147 ca_proplist_sets(proplist, CA_PROP_EVENT_ID,
148 (char *)tv_get_string(&argvars[0]));
149 ca_proplist_sets(proplist, CA_PROP_CANBERRA_CACHE_CONTROL,
150 "volatile");
151 res = ca_context_play_full(context, sound_id, proplist,
152 sound_callback, soundcb);
153 if (res != CA_SUCCESS)
154 delete_sound_callback(soundcb);
155
156 ca_proplist_destroy(proplist);
157 }
158 }
159 rettv->vval.v_number = res == CA_SUCCESS ? sound_id : 0;
160 }
161}
162
163 void
164f_sound_playevent(typval_T *argvars, typval_T *rettv)
165{
166 sound_play_common(argvars, rettv, FALSE);
167}
168
Bram Moolenaar3ff5f0f2019-06-10 13:11:22 +0200169/*
170 * implementation of sound_playfile({path} [, {callback}])
171 */
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200172 void
173f_sound_playfile(typval_T *argvars, typval_T *rettv)
174{
175 sound_play_common(argvars, rettv, TRUE);
176}
177
Bram Moolenaar3ff5f0f2019-06-10 13:11:22 +0200178/*
179 * implementation of sound_stop({id})
180 */
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200181 void
182f_sound_stop(typval_T *argvars, typval_T *rettv UNUSED)
183{
184 if (context != NULL)
185 ca_context_cancel(context, tv_get_number(&argvars[0]));
186}
187
Bram Moolenaar3ff5f0f2019-06-10 13:11:22 +0200188/*
189 * implementation of sound_clear()
190 */
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200191 void
Bram Moolenaar3ff5f0f2019-06-10 13:11:22 +0200192f_sound_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200193{
194 if (context != NULL)
195 {
196 ca_context_destroy(context);
197 context = NULL;
198 }
199}
200
Bram Moolenaar9b283522019-06-17 22:19:33 +0200201# if defined(EXITFREE) || defined(PROTO)
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200202 void
203sound_free(void)
204{
205 if (context != NULL)
206 ca_context_destroy(context);
207 while (first_callback != NULL)
208 delete_sound_callback(first_callback);
209}
Bram Moolenaar9b283522019-06-17 22:19:33 +0200210# endif
Bram Moolenaar427f5b62019-06-09 13:43:51 +0200211
Bram Moolenaar9b283522019-06-17 22:19:33 +0200212#elif defined(MSWIN)
213
214/*
215 * Sound implementation for MS-Windows.
216 */
217
218static HWND g_hWndSound = NULL;
219
220 static LRESULT CALLBACK
221sound_wndproc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
222{
223 soundcb_T *p;
224
225 switch (message)
226 {
227 case MM_MCINOTIFY:
228 for (p = first_callback; p != NULL; p = p->snd_next)
229 if (p->snd_device_id == (MCIDEVICEID) lParam)
230 {
231 typval_T argv[3];
232 typval_T rettv;
233 int dummy;
234 char buf[32];
235
236 vim_snprintf(buf, sizeof(buf), "close sound%06ld",
237 p->snd_id);
238 mciSendString(buf, NULL, 0, 0);
239
240 argv[0].v_type = VAR_NUMBER;
241 argv[0].vval.v_number = p->snd_id;
242 argv[1].v_type = VAR_NUMBER;
243 argv[1].vval.v_number =
244 wParam == MCI_NOTIFY_SUCCESSFUL ? 0
245 : wParam == MCI_NOTIFY_ABORTED ? 1 : 2;
246 argv[2].v_type = VAR_UNKNOWN;
247
248 call_callback(&p->snd_callback, -1,
249 &rettv, 2, argv, NULL, 0L, 0L, &dummy, TRUE, NULL);
250 clear_tv(&rettv);
251
252 delete_sound_callback(p);
253 redraw_after_callback(TRUE);
254
255 }
256 break;
257 }
258
259 return DefWindowProc(hwnd, message, wParam, lParam);
260}
261
262 static HWND
263sound_window()
264{
265 if (g_hWndSound == NULL)
266 {
267 LPCSTR clazz = "VimSound";
268 WNDCLASS wndclass = {
269 0, sound_wndproc, 0, 0, g_hinst, NULL, 0, 0, NULL, clazz };
270 RegisterClass(&wndclass);
271 g_hWndSound = CreateWindow(clazz, NULL, 0, 0, 0, 0, 0,
272 HWND_MESSAGE, NULL, g_hinst, NULL);
273 }
274
275 return g_hWndSound;
276}
277
278 void
279f_sound_playevent(typval_T *argvars, typval_T *rettv)
280{
281 WCHAR *wp;
282
283 rettv->v_type = VAR_NUMBER;
284 rettv->vval.v_number = 0;
285
286 wp = enc_to_utf16(tv_get_string(&argvars[0]), NULL);
287 if (wp == NULL)
288 return;
289
290 PlaySoundW(wp, NULL, SND_ASYNC | SND_ALIAS);
291 free(wp);
292
293 rettv->vval.v_number = ++sound_id;
294}
295
296 void
297f_sound_playfile(typval_T *argvars, typval_T *rettv)
298{
299 long newid = sound_id + 1;
300 size_t len;
301 char_u *p, *esc;
302 WCHAR *wp;
303 soundcb_T *soundcb;
304 char buf[32];
305 MCIERROR err;
306
307 rettv->v_type = VAR_NUMBER;
308 rettv->vval.v_number = 0;
309
310 esc = vim_strsave_shellescape(tv_get_string(&argvars[0]), FALSE, FALSE);
311
312 len = STRLEN(esc) + 5 + 18 + 1;
313 p = alloc(len);
314 if (p == NULL)
315 {
316 free(esc);
317 return;
318 }
319 vim_snprintf((char *)p, len, "open %s alias sound%06ld", esc, newid);
320 free(esc);
321
322 wp = enc_to_utf16((char_u *)p, NULL);
323 free(p);
324 if (wp == NULL)
325 return;
326
327 err = mciSendStringW(wp, NULL, 0, sound_window());
328 free(wp);
329 if (err != 0)
330 return;
331
332 vim_snprintf(buf, sizeof(buf), "play sound%06ld notify", newid);
333 err = mciSendString(buf, NULL, 0, sound_window());
334 if (err != 0)
335 goto failure;
336
337 sound_id = newid;
338 rettv->vval.v_number = sound_id;
339
340 soundcb = get_sound_callback(&argvars[1]);
341 if (soundcb != NULL)
342 {
343 vim_snprintf(buf, sizeof(buf), "sound%06ld", newid);
344 soundcb->snd_id = newid;
345 soundcb->snd_device_id = mciGetDeviceID(buf);
346 }
347 return;
348
349failure:
350 vim_snprintf(buf, sizeof(buf), "close sound%06ld", newid);
351 mciSendString(buf, NULL, 0, NULL);
352}
353
354 void
355f_sound_stop(typval_T *argvars, typval_T *rettv UNUSED)
356{
357 long id = tv_get_number(&argvars[0]);
358 char buf[32];
359
360 vim_snprintf(buf, sizeof(buf), "stop sound%06ld", id);
361 mciSendString(buf, NULL, 0, NULL);
362}
363
364 void
365f_sound_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
366{
367 PlaySound(NULL, NULL, 0);
368 mciSendString("close all", NULL, 0, NULL);
369}
370
371# if defined(EXITFREE)
372 void
373sound_free(void)
374{
375 CloseWindow(g_hWndSound);
376
377 while (first_callback != NULL)
378 delete_sound_callback(first_callback);
379}
380# endif
381
382#endif // MSWIN
383
384#endif // FEAT_SOUND