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 38 has_any_sound_callback(void) 39 { 40 return first_callback != NULL; 41 } 42 43 static soundcb_T * 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 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 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 143 has_sound_callback_in_queue(void) 144 { 145 return callback_queue != NULL; 146 } 147 148 /* 149 * Invoke queued sound callbacks. 150 */ 151 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 180 sound_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 226 f_sound_playevent(typval_T *argvars, typval_T *rettv) 227 { 228 sound_play_common(argvars, rettv, FALSE); 229 } 230 231 /* 232 * implementation of sound_playfile({path} [, {callback}]) 233 */ 234 void 235 f_sound_playfile(typval_T *argvars, typval_T *rettv) 236 { 237 sound_play_common(argvars, rettv, TRUE); 238 } 239 240 /* 241 * implementation of sound_stop({id}) 242 */ 243 void 244 f_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 250 /* 251 * implementation of sound_clear() 252 */ 253 void 254 f_sound_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED) 255 { 256 if (context != NULL) 257 { 258 ca_context_destroy(context); 259 context = NULL; 260 } 261 } 262 263 # if defined(EXITFREE) || defined(PROTO) 264 void 265 sound_free(void) 266 { 267 soundcb_queue_T *scb; 268 269 if (context != NULL) 270 ca_context_destroy(context); 271 272 while (first_callback != NULL) 273 delete_sound_callback(first_callback); 274 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 } 282 } 283 # endif 284 285 #elif defined(MSWIN) 286 287 /* 288 * Sound implementation for MS-Windows. 289 */ 290 291 static HWND g_hWndSound = NULL; 292 293 static LRESULT CALLBACK 294 sound_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; 306 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 320 call_callback(&p->snd_callback, -1, &rettv, 2, argv); 321 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 334 sound_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 350 f_sound_playevent(typval_T *argvars, typval_T *rettv) 351 { 352 WCHAR *wp; 353 354 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 365 f_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 375 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 414 failure: 415 vim_snprintf(buf, sizeof(buf), "close sound%06ld", newid); 416 mciSendString(buf, NULL, 0, NULL); 417 } 418 419 void 420 f_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 430 f_sound_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED) 431 { 432 PlaySoundW(NULL, NULL, 0); 433 mciSendString("close all", NULL, 0, NULL); 434 } 435 436 # if defined(EXITFREE) 437 void 438 sound_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 450