blob: 92618a9f341a65e6a5c989a8fd8153074c2b28d8 [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;
Bram Moolenaar9b283522019-06-17 22:19:33 +0200231 char buf[32];
232
233 vim_snprintf(buf, sizeof(buf), "close sound%06ld",
234 p->snd_id);
235 mciSendString(buf, NULL, 0, 0);
236
237 argv[0].v_type = VAR_NUMBER;
238 argv[0].vval.v_number = p->snd_id;
239 argv[1].v_type = VAR_NUMBER;
240 argv[1].vval.v_number =
241 wParam == MCI_NOTIFY_SUCCESSFUL ? 0
242 : wParam == MCI_NOTIFY_ABORTED ? 1 : 2;
243 argv[2].v_type = VAR_UNKNOWN;
244
Bram Moolenaarb2129062019-08-03 18:31:11 +0200245 call_callback(&p->snd_callback, -1, &rettv, 2, argv);
Bram Moolenaar9b283522019-06-17 22:19:33 +0200246 clear_tv(&rettv);
247
248 delete_sound_callback(p);
249 redraw_after_callback(TRUE);
250
251 }
252 break;
253 }
254
255 return DefWindowProc(hwnd, message, wParam, lParam);
256}
257
258 static HWND
259sound_window()
260{
261 if (g_hWndSound == NULL)
262 {
263 LPCSTR clazz = "VimSound";
264 WNDCLASS wndclass = {
265 0, sound_wndproc, 0, 0, g_hinst, NULL, 0, 0, NULL, clazz };
266 RegisterClass(&wndclass);
267 g_hWndSound = CreateWindow(clazz, NULL, 0, 0, 0, 0, 0,
268 HWND_MESSAGE, NULL, g_hinst, NULL);
269 }
270
271 return g_hWndSound;
272}
273
274 void
275f_sound_playevent(typval_T *argvars, typval_T *rettv)
276{
277 WCHAR *wp;
278
279 rettv->v_type = VAR_NUMBER;
280 rettv->vval.v_number = 0;
281
282 wp = enc_to_utf16(tv_get_string(&argvars[0]), NULL);
283 if (wp == NULL)
284 return;
285
286 PlaySoundW(wp, NULL, SND_ASYNC | SND_ALIAS);
287 free(wp);
288
289 rettv->vval.v_number = ++sound_id;
290}
291
292 void
293f_sound_playfile(typval_T *argvars, typval_T *rettv)
294{
295 long newid = sound_id + 1;
296 size_t len;
297 char_u *p, *esc;
298 WCHAR *wp;
299 soundcb_T *soundcb;
300 char buf[32];
301 MCIERROR err;
302
303 rettv->v_type = VAR_NUMBER;
304 rettv->vval.v_number = 0;
305
306 esc = vim_strsave_shellescape(tv_get_string(&argvars[0]), FALSE, FALSE);
307
308 len = STRLEN(esc) + 5 + 18 + 1;
309 p = alloc(len);
310 if (p == NULL)
311 {
312 free(esc);
313 return;
314 }
315 vim_snprintf((char *)p, len, "open %s alias sound%06ld", esc, newid);
316 free(esc);
317
318 wp = enc_to_utf16((char_u *)p, NULL);
319 free(p);
320 if (wp == NULL)
321 return;
322
323 err = mciSendStringW(wp, NULL, 0, sound_window());
324 free(wp);
325 if (err != 0)
326 return;
327
328 vim_snprintf(buf, sizeof(buf), "play sound%06ld notify", newid);
329 err = mciSendString(buf, NULL, 0, sound_window());
330 if (err != 0)
331 goto failure;
332
333 sound_id = newid;
334 rettv->vval.v_number = sound_id;
335
336 soundcb = get_sound_callback(&argvars[1]);
337 if (soundcb != NULL)
338 {
339 vim_snprintf(buf, sizeof(buf), "sound%06ld", newid);
340 soundcb->snd_id = newid;
341 soundcb->snd_device_id = mciGetDeviceID(buf);
342 }
343 return;
344
345failure:
346 vim_snprintf(buf, sizeof(buf), "close sound%06ld", newid);
347 mciSendString(buf, NULL, 0, NULL);
348}
349
350 void
351f_sound_stop(typval_T *argvars, typval_T *rettv UNUSED)
352{
353 long id = tv_get_number(&argvars[0]);
354 char buf[32];
355
356 vim_snprintf(buf, sizeof(buf), "stop sound%06ld", id);
357 mciSendString(buf, NULL, 0, NULL);
358}
359
360 void
361f_sound_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
362{
363 PlaySound(NULL, NULL, 0);
364 mciSendString("close all", NULL, 0, NULL);
365}
366
367# if defined(EXITFREE)
368 void
369sound_free(void)
370{
371 CloseWindow(g_hWndSound);
372
373 while (first_callback != NULL)
374 delete_sound_callback(first_callback);
375}
376# endif
377
378#endif // MSWIN
379
380#endif // FEAT_SOUND