pop 2aca846ce15eecb13ed27a247389e46952e6fa4a
[wine/hacks.git] / dlls / winepulse.drv / wavein.c
blob7cbc781bf35226aaede83dd95e6482e43fe981c2
1 /*
2 * Wine Driver for PulseAudio - WaveIn Functionality
3 * http://pulseaudio.org/
5 * Copyright 2009 Arthur Taylor <theycallhimart@gmail.com>
7 * Contains code from other wine multimedia drivers.
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 #include "config.h"
26 #include <stdarg.h>
28 #include "windef.h"
29 #include "winbase.h"
30 #include "wingdi.h"
31 #include "winuser.h"
32 #include "winnls.h"
33 #include "mmddk.h"
35 #include <winepulse.h>
37 #include "wine/debug.h"
39 WINE_DEFAULT_DEBUG_CHANNEL(wave);
41 #if HAVE_PULSEAUDIO
43 /*======================================================================*
44 * WAVE IN specific PulseAudio Callbacks *
45 *======================================================================*/
47 /**************************************************************************
48 * widNotifyClient [internal]
50 static DWORD widNotifyClient(WINE_WAVEINST* wwi, WORD wMsg, DWORD dwParam1, DWORD dwParam2) {
51 TRACE("wMsg = 0x%04x dwParm1 = %04X dwParam2 = %04X\n", wMsg, dwParam1, dwParam2);
53 switch (wMsg) {
54 case WIM_OPEN:
55 case WIM_CLOSE:
56 case WIM_DATA:
57 if (wwi->wFlags != DCB_NULL &&
58 !DriverCallback(wwi->waveDesc.dwCallback, wwi->wFlags, (HDRVR)wwi->waveDesc.hWave,
59 wMsg, wwi->waveDesc.dwInstance, dwParam1, dwParam2)) {
60 WARN("can't notify client !\n");
61 return MMSYSERR_ERROR;
63 break;
64 default:
65 FIXME("Unknown callback message %u\n", wMsg);
66 return MMSYSERR_INVALPARAM;
68 return MMSYSERR_NOERROR;
71 /**************************************************************************
72 * widRecorder_NextFragment [internal]
74 * Gets the next fragment of data from the server.
76 static size_t widRecorder_NextFragment(WINE_WAVEINST *wwi) {
77 size_t nbytes;
79 TRACE("()\n");
81 if (wwi->buffer)
82 pa_stream_drop(wwi->stream);
84 pa_stream_peek(wwi->stream, &wwi->buffer, &nbytes);
85 wwi->buffer_length = nbytes;
86 wwi->buffer_read_offset = 0;
88 return nbytes;
92 /**************************************************************************
93 * widRecorder_CopyData [internal]
95 * Copys data from the fragments pulse returns to queued buffers.
97 static void widRecorder_CopyData(WINE_WAVEINST *wwi) {
98 LPWAVEHDR lpWaveHdr = wwi->lpQueuePtr;
99 size_t nbytes;
101 while (lpWaveHdr && wwi->state == WINE_WS_PLAYING) {
103 nbytes = min(wwi->buffer_length - wwi->buffer_read_offset, lpWaveHdr->dwBufferLength - lpWaveHdr->dwBytesRecorded);
104 if (nbytes == 0) break;
106 TRACE("%u bytes from %p to %p\n",
107 nbytes,
108 (PBYTE)wwi->buffer + wwi->buffer_read_offset,
109 lpWaveHdr->lpData + lpWaveHdr->dwBytesRecorded);
111 memcpy(lpWaveHdr->lpData + lpWaveHdr->dwBytesRecorded, (PBYTE)wwi->buffer + wwi->buffer_read_offset, nbytes);
113 lpWaveHdr->dwBytesRecorded += nbytes;
114 wwi->buffer_read_offset += nbytes;
116 if (wwi->buffer_read_offset == wwi->buffer_length) {
117 pa_threaded_mainloop_lock(PULSE_ml);
118 pa_stream_drop(wwi->stream);
119 if (pa_stream_readable_size(wwi->stream))
120 widRecorder_NextFragment(wwi);
121 else {
122 wwi->buffer = NULL;
123 wwi->buffer_length = 0;
124 wwi->buffer_read_offset = 0;
126 pa_threaded_mainloop_unlock(PULSE_ml);
129 if (lpWaveHdr->dwBytesRecorded == lpWaveHdr->dwBufferLength) {
130 lpWaveHdr->dwFlags &= ~WHDR_INQUEUE;
131 lpWaveHdr->dwFlags |= WHDR_DONE;
132 wwi->lpQueuePtr = lpWaveHdr->lpNext;
133 widNotifyClient(wwi, WIM_DATA, (DWORD)lpWaveHdr, 0);
134 lpWaveHdr = wwi->lpQueuePtr;
139 /**************************************************************************
140 * widRecorder [internal]
142 static DWORD CALLBACK widRecorder(LPVOID lpParam) {
143 WINE_WAVEINST *wwi = (WINE_WAVEINST*)lpParam;
144 LPWAVEHDR lpWaveHdr;
145 enum win_wm_message msg;
146 DWORD param;
147 HANDLE ev;
148 DWORD wait = INFINITE;
150 wwi->state = WINE_WS_STOPPED;
151 SetEvent(wwi->hStartUpEvent);
153 for (;;) {
155 if (wwi->state != WINE_WS_PLAYING) {
156 wait = INFINITE;
157 } else {
158 if (wwi->buffer == NULL && pa_stream_readable_size(wwi->stream)) {
159 pa_threaded_mainloop_lock(PULSE_ml);
160 wait = pa_bytes_to_usec(widRecorder_NextFragment(wwi), &wwi->sample_spec)/1000;
161 pa_threaded_mainloop_unlock(PULSE_ml);
165 widRecorder_CopyData(wwi);
167 PULSE_WaitRingMessage(&wwi->msgRing, wait);
169 while (PULSE_RetrieveRingMessage(&wwi->msgRing, &msg, &param, &ev)) {
170 TRACE("Received %s %x\n", PULSE_getCmdString(msg), param);
172 switch (msg) {
173 case WINE_WM_FEED:
174 SetEvent(ev);
175 break;
176 case WINE_WM_STARTING:
177 wwi->state = WINE_WS_PLAYING;
178 if (wwi->lpQueuePtr)
179 wait = pa_bytes_to_usec(wwi->lpQueuePtr->dwBufferLength, &wwi->sample_spec)/1000;
180 else
181 wait = INFINITE;
182 wwi->dwLastReset = wwi->timing_info->read_index;
183 pa_threaded_mainloop_lock(PULSE_ml);
184 PULSE_WaitForOperation(pa_stream_cork(wwi->stream, 0, PULSE_StreamSuccessCallback, NULL));
185 pa_threaded_mainloop_unlock(PULSE_ml);
186 SetEvent(ev);
187 break;
188 case WINE_WM_HEADER:
189 lpWaveHdr = (LPWAVEHDR)param;
190 lpWaveHdr->lpNext = 0;
192 /* insert buffer at the end of queue */
194 LPWAVEHDR *wh;
195 for (wh = &(wwi->lpQueuePtr); *wh; wh = &((*wh)->lpNext));
196 *wh = lpWaveHdr;
198 break;
199 case WINE_WM_STOPPING:
200 if (wwi->state != WINE_WS_STOPPED) {
201 wwi->state = WINE_WS_STOPPED;
202 pa_threaded_mainloop_lock(PULSE_ml);
203 PULSE_WaitForOperation(pa_stream_cork(wwi->stream, 1, PULSE_StreamSuccessCallback, NULL));
204 pa_threaded_mainloop_unlock(PULSE_ml);
206 /* return current buffer to app */
207 lpWaveHdr = wwi->lpQueuePtr;
208 if (lpWaveHdr) {
209 LPWAVEHDR lpNext = lpWaveHdr->lpNext;
210 TRACE("stop %p %p\n", lpWaveHdr, lpWaveHdr->lpNext);
211 lpWaveHdr->dwFlags &= ~WHDR_INQUEUE;
212 lpWaveHdr->dwFlags |= WHDR_DONE;
213 wwi->lpQueuePtr = lpNext;
214 widNotifyClient(wwi, WIM_DATA, (DWORD)lpWaveHdr, 0);
217 SetEvent(ev);
218 break;
219 case WINE_WM_RESETTING:
220 if (wwi->state != WINE_WS_STOPPED) {
221 wwi->state = WINE_WS_STOPPED;
222 pa_threaded_mainloop_lock(PULSE_ml);
223 PULSE_WaitForOperation(pa_stream_cork(wwi->stream, 1, PULSE_StreamSuccessCallback, NULL));
224 pa_threaded_mainloop_unlock(PULSE_ml);
227 /* return all buffers to the app */
228 for (lpWaveHdr = wwi->lpPlayPtr ? wwi->lpPlayPtr : wwi->lpQueuePtr; lpWaveHdr; lpWaveHdr = wwi->lpQueuePtr) {
229 lpWaveHdr->dwFlags &= ~WHDR_INQUEUE;
230 lpWaveHdr->dwFlags |= WHDR_DONE;
231 wwi->lpQueuePtr = lpWaveHdr->lpNext;
232 widNotifyClient(wwi, WIM_DATA, (DWORD)lpWaveHdr, 0);
235 SetEvent(ev);
236 break;
237 case WINE_WM_CLOSING:
238 wwi->hThread = 0;
239 if ((DWORD)param == 1) {
240 /* If we are here, the stream failed */
241 wwi->state = WINE_WS_FAILED;
242 SetEvent(ev);
243 PULSE_DestroyRingMessage(&wwi->msgRing);
244 widNotifyClient(wwi, WIM_CLOSE, 0L, 0L);
245 wwi->lpPlayPtr = wwi->lpQueuePtr = NULL;
246 pa_threaded_mainloop_lock(PULSE_ml);
247 pa_stream_disconnect(wwi->stream);
248 pa_threaded_mainloop_unlock(PULSE_ml);
249 TRACE("Thread exiting because of failure.\n");
250 ExitThread(1);
252 wwi->state = WINE_WS_CLOSED;
253 SetEvent(ev);
254 ExitThread(0);
255 /* shouldn't go here */
256 default:
257 FIXME("unknown message %d\n", msg);
258 break;
259 } /* switch(msg) */
260 } /* while(PULSE_RetrieveRingMessage()) */
261 } /* for (;;) */
264 /**************************************************************************
265 * widOpen [internal]
267 static DWORD widOpen(WORD wDevID, DWORD_PTR *lpdwUser, LPWAVEOPENDESC lpDesc, DWORD dwFlags) {
268 WINE_WAVEDEV *wdi;
269 WINE_WAVEINST *wwi = NULL;
270 DWORD ret = MMSYSERR_NOERROR;
272 TRACE("(%u, %p, %08X);\n", wDevID, lpDesc, dwFlags);
273 if (lpDesc == NULL) {
274 WARN("Invalid Parameter !\n");
275 return MMSYSERR_INVALPARAM;
278 if (wDevID >= PULSE_WidNumDevs) {
279 TRACE("Asked for device %d, but only %d known!\n", wDevID, PULSE_WidNumDevs);
280 return MMSYSERR_BADDEVICEID;
282 wdi = &WInDev[wDevID];
284 wwi = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WINE_WAVEINST));
285 if (!wwi) return MMSYSERR_NOMEM;
286 *lpdwUser = (DWORD_PTR)wwi;
288 /* check to see if format is supported and make pa_sample_spec struct */
289 if (!PULSE_SetupFormat(lpDesc->lpFormat, &wwi->sample_spec)) {
290 WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
291 lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
292 lpDesc->lpFormat->nSamplesPerSec);
293 ret = WAVERR_BADFORMAT;
294 goto exit;
297 if (TRACE_ON(wave)) {
298 char t[PA_SAMPLE_SPEC_SNPRINT_MAX];
299 pa_sample_spec_snprint(t, sizeof(t), &wwi->sample_spec);
300 TRACE("Sample spec '%s'\n", t);
303 if (dwFlags & WAVE_FORMAT_QUERY) {
304 TRACE("Query format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
305 lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
306 lpDesc->lpFormat->nSamplesPerSec);
307 ret = MMSYSERR_NOERROR;
308 goto exit;
311 wwi->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK);
312 wwi->waveDesc = *lpDesc;
313 PULSE_InitRingMessage(&wwi->msgRing);
315 wwi->stream = pa_stream_new(PULSE_context, "WaveIn", &wwi->sample_spec, NULL);
316 if (!wwi->stream) {
317 ret = WAVERR_BADFORMAT;
318 goto exit;
321 pa_stream_set_state_callback(wwi->stream, PULSE_StreamStateCallback, wwi);
323 wwi->buffer_attr.maxlength = (uint32_t)-1;
324 wwi->buffer_attr.fragsize = pa_bytes_per_second(&wwi->sample_spec) / 100;
326 pa_threaded_mainloop_lock(PULSE_ml);
327 TRACE("Asking to open %s for recording.\n", wdi->device_name);
328 pa_stream_connect_record(wwi->stream, wdi->device_name, &wwi->buffer_attr,
329 PA_STREAM_START_CORKED |
330 PA_STREAM_AUTO_TIMING_UPDATE);
332 for (;;) {
333 pa_context_state_t cstate = pa_context_get_state(PULSE_context);
334 pa_stream_state_t sstate = pa_stream_get_state(wwi->stream);
336 if (cstate == PA_CONTEXT_FAILED || cstate == PA_CONTEXT_TERMINATED ||
337 sstate == PA_STREAM_FAILED || sstate == PA_STREAM_TERMINATED) {
338 ERR("Failed to connect context object: %s\n", pa_strerror(pa_context_errno(PULSE_context)));
339 ret = MMSYSERR_NODRIVER;
340 pa_threaded_mainloop_unlock(PULSE_ml);
341 goto exit;
344 if (sstate == PA_STREAM_READY)
345 break;
347 pa_threaded_mainloop_wait(PULSE_ml);
349 TRACE("(%p)->stream connected for recording.\n", wwi);
351 PULSE_WaitForOperation(pa_stream_update_timing_info(wwi->stream, PULSE_StreamSuccessCallback, wwi));
353 wwi->timing_info = pa_stream_get_timing_info(wwi->stream);
354 assert(wwi->timing_info);
355 pa_threaded_mainloop_unlock(PULSE_ml);
357 wwi->hStartUpEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
358 wwi->hThread = CreateThread(NULL, 0, widRecorder, (LPVOID)wwi, 0, &(wwi->dwThreadID));
359 if (wwi->hThread)
360 SetThreadPriority(wwi->hThread, THREAD_PRIORITY_TIME_CRITICAL);
361 else {
362 ERR("Thread creation for the widRecorder failed!\n");
363 ret = MMSYSERR_NOMEM;
364 goto exit;
366 WaitForSingleObject(wwi->hStartUpEvent, INFINITE);
367 CloseHandle(wwi->hStartUpEvent);
368 wwi->hStartUpEvent = INVALID_HANDLE_VALUE;
370 return widNotifyClient(wwi, WIM_OPEN, 0L, 0L);
372 exit:
373 if (!wwi)
374 return ret;
376 if (wwi->hStartUpEvent != INVALID_HANDLE_VALUE)
377 CloseHandle(wwi->hStartUpEvent);
379 if (wwi->msgRing.ring_buffer_size > 0)
380 PULSE_DestroyRingMessage(&wwi->msgRing);
382 if (wwi->stream) {
383 if (pa_stream_get_state(wwi->stream) == PA_STREAM_READY)
384 pa_stream_disconnect(wwi->stream);
385 pa_stream_unref(wwi->stream);
387 HeapFree(GetProcessHeap(), 0, wwi);
389 return ret;
391 /**************************************************************************
392 * widClose [internal]
394 static DWORD widClose(WORD wDevID, WINE_WAVEINST *wwi) {
395 DWORD ret;
397 TRACE("(%u, %p);\n", wDevID, wwi);
398 if (wDevID >= PULSE_WidNumDevs) {
399 WARN("Asked for device %d, but only %d known!\n", wDevID, PULSE_WodNumDevs);
400 return MMSYSERR_INVALHANDLE;
401 } else if (!wwi) {
402 WARN("Stream instance invalid.\n");
403 return MMSYSERR_INVALHANDLE;
406 if (wwi->state != WINE_WS_FAILED) {
407 if (wwi->lpQueuePtr) {
408 WARN("buffers recording recording !\n");
409 return WAVERR_STILLPLAYING;
412 pa_threaded_mainloop_lock(PULSE_ml);
413 if (pa_stream_get_state(wwi->stream) == PA_STREAM_READY)
414 pa_stream_drop(wwi->stream);
415 pa_stream_disconnect(wwi->stream);
416 pa_threaded_mainloop_unlock(PULSE_ml);
418 if (wwi->hThread != INVALID_HANDLE_VALUE)
419 PULSE_AddRingMessage(&wwi->msgRing, WINE_WM_CLOSING, 0, TRUE);
421 PULSE_DestroyRingMessage(&wwi->msgRing);
423 ret = widNotifyClient(wwi, WIM_CLOSE, 0L, 0L);
425 pa_stream_unref(wwi->stream);
426 TRACE("Deallocating record instance.\n");
427 HeapFree(GetProcessHeap(), 0, wwi);
428 return ret;
431 /**************************************************************************
432 * widAddBuffer [internal]
435 static DWORD widAddBuffer(WINE_WAVEINST* wwi, LPWAVEHDR lpWaveHdr, DWORD dwSize) {
436 TRACE("(%p, %p, %08X);\n", wwi, lpWaveHdr, dwSize);
438 if (!wwi || wwi->state == WINE_WS_FAILED) {
439 WARN("Stream instance invalid.\n");
440 return MMSYSERR_INVALHANDLE;
443 if (lpWaveHdr->lpData == NULL || !(lpWaveHdr->dwFlags & WHDR_PREPARED))
444 return WAVERR_UNPREPARED;
446 if (lpWaveHdr->dwFlags & WHDR_INQUEUE)
447 return WAVERR_STILLPLAYING;
449 lpWaveHdr->dwFlags &= ~WHDR_DONE;
450 lpWaveHdr->dwFlags |= WHDR_INQUEUE;
451 lpWaveHdr->dwBytesRecorded = 0;
452 lpWaveHdr->lpNext = 0;
454 PULSE_AddRingMessage(&wwi->msgRing, WINE_WM_HEADER, (DWORD)lpWaveHdr, FALSE);
456 return MMSYSERR_NOERROR;
459 /**************************************************************************
460 * widRecorderMessage [internal]
462 static DWORD widRecorderMessage(WINE_WAVEINST *wwi, enum win_wm_message message) {
463 if (!wwi || wwi->state == WINE_WS_FAILED) {
464 WARN("Stream instance invalid.\n");
465 return MMSYSERR_INVALHANDLE;
468 PULSE_AddRingMessage(&wwi->msgRing, message, 0, TRUE);
469 return MMSYSERR_NOERROR;
472 /**************************************************************************
473 * widGetPosition [internal]
475 static DWORD widGetPosition(WINE_WAVEINST *wwi, LPMMTIME lpTime, DWORD uSize) {
477 if (!wwi || wwi->state == WINE_WS_FAILED) {
478 WARN("Stream instance invalid.\n");
479 return MMSYSERR_INVALHANDLE;
482 if (lpTime == NULL) return MMSYSERR_INVALPARAM;
484 return PULSE_UsecToMMTime(pa_bytes_to_usec(wwi->timing_info->read_index - wwi->dwLastReset, &wwi->sample_spec), lpTime, &wwi->sample_spec);
487 /**************************************************************************
488 * widGetDevCaps [internal]
490 static DWORD widGetDevCaps(DWORD wDevID, LPWAVEINCAPSW lpCaps, DWORD dwSize) {
491 TRACE("(%u, %p, %u);\n", wDevID, lpCaps, dwSize);
493 if (lpCaps == NULL) return MMSYSERR_NOTENABLED;
495 if (wDevID >= PULSE_WidNumDevs) {
496 TRACE("Asked for device %d, but only %d known!\n", wDevID, PULSE_WidNumDevs);
497 return MMSYSERR_INVALHANDLE;
500 memcpy(lpCaps, &(WInDev[wDevID].caps.in), min(dwSize, sizeof(*lpCaps)));
501 return MMSYSERR_NOERROR;
504 /**************************************************************************
505 * widGetNumDevs [internal]
506 * Context-sanity check here, as if we respond with 0, WINE will move on
507 * to the next wavein driver.
509 static DWORD widGetNumDevs() {
510 if (pa_context_get_state(PULSE_context) != PA_CONTEXT_READY)
511 return 0;
513 return PULSE_WidNumDevs;
516 /**************************************************************************
517 * widDevInterfaceSize [internal]
519 static DWORD widDevInterfaceSize(UINT wDevID, LPDWORD dwParam1) {
520 TRACE("(%u, %p)\n", wDevID, dwParam1);
522 *dwParam1 = MultiByteToWideChar(CP_UNIXCP, 0, WInDev[wDevID].interface_name, -1,
523 NULL, 0 ) * sizeof(WCHAR);
524 return MMSYSERR_NOERROR;
527 /**************************************************************************
528 * widDevInterface [internal]
530 static DWORD widDevInterface(UINT wDevID, PWCHAR dwParam1, DWORD dwParam2) {
531 if (dwParam2 >= MultiByteToWideChar(CP_UNIXCP, 0, WInDev[wDevID].interface_name, -1,
532 NULL, 0 ) * sizeof(WCHAR))
534 MultiByteToWideChar(CP_UNIXCP, 0, WInDev[wDevID].interface_name, -1,
535 dwParam1, dwParam2 / sizeof(WCHAR));
536 return MMSYSERR_NOERROR;
538 return MMSYSERR_INVALPARAM;
541 /**************************************************************************
542 * widDsDesc [internal]
544 DWORD widDsDesc(UINT wDevID, PDSDRIVERDESC desc)
546 *desc = WInDev[wDevID].ds_desc;
547 return MMSYSERR_NOERROR;
550 /**************************************************************************
551 * widMessage (WINEPULSE.@)
553 DWORD WINAPI PULSE_widMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser,
554 DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
556 switch (wMsg) {
557 case DRVM_INIT:
558 case DRVM_EXIT:
559 case DRVM_ENABLE:
560 case DRVM_DISABLE:
561 /* FIXME: Pretend this is supported */
562 return 0;
563 case WIDM_OPEN: return widOpen (wDevID, (DWORD_PTR*)dwUser, (LPWAVEOPENDESC)dwParam1, dwParam2);
564 case WIDM_CLOSE: return widClose (wDevID, (WINE_WAVEINST*)dwUser);
565 case WIDM_ADDBUFFER: return widAddBuffer ((WINE_WAVEINST*)dwUser, (LPWAVEHDR)dwParam1, dwParam2);
566 case WIDM_PREPARE: return MMSYSERR_NOTSUPPORTED;
567 case WIDM_UNPREPARE: return MMSYSERR_NOTSUPPORTED;
568 case WIDM_GETDEVCAPS: return widGetDevCaps(wDevID, (LPWAVEINCAPSW)dwParam1, dwParam2);
569 case WIDM_GETNUMDEVS: return widGetNumDevs();
570 case WIDM_GETPOS: return widGetPosition ((WINE_WAVEINST*)dwUser, (LPMMTIME)dwParam1, dwParam2);
571 case WIDM_RESET: return widRecorderMessage((WINE_WAVEINST*)dwUser, WINE_WM_RESETTING);
572 case WIDM_START: return widRecorderMessage((WINE_WAVEINST*)dwUser, WINE_WM_STARTING);
573 case WIDM_STOP: return widRecorderMessage((WINE_WAVEINST*)dwUser, WINE_WM_STOPPING);
574 case DRV_QUERYDEVICEINTERFACESIZE: return widDevInterfaceSize(wDevID, (LPDWORD)dwParam1);
575 case DRV_QUERYDEVICEINTERFACE: return widDevInterface(wDevID, (PWCHAR)dwParam1, dwParam2);
576 case DRV_QUERYDSOUNDIFACE: return MMSYSERR_NOTSUPPORTED; /* Use emulation, as there is no advantage */
577 case DRV_QUERYDSOUNDDESC: return widDsDesc(wDevID, (PDSDRIVERDESC)dwParam1);
578 default:
579 FIXME("unknown message %d!\n", wMsg);
581 return MMSYSERR_NOTSUPPORTED;
584 #else /* HAVE_PULSEAUDIO */
586 /**************************************************************************
587 * widMessage (WINEPULSE.@)
589 DWORD WINAPI PULSE_widMessage(WORD wDevID, WORD wMsg, DWORD dwUser,
590 DWORD dwParam1, DWORD dwParam2) {
591 FIXME("(%u, %04X, %08X, %08X, %08X):stub\n", wDevID, wMsg, dwUser, dwParam1, dwParam2);
592 return MMSYSERR_NOTENABLED;
595 #endif /* HAVE_PULSEAUDIO */