diff --git a/README.md b/README.md index 198f05dc..d3eb1a3d 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,11 @@ Wine. All those differences are also documented on the Included bug fixes and improvements =================================== +**Bugfixes and features included in the next upcoming release [1]:** + +* Allow selection of audio device for PulseAudio backend + + **Bugs fixed in Wine Staging 1.7.30 [90]:** * ATL IOCS data should not be stored in GWLP_USERDATA ([Wine Bug #21767](https://bugs.winehq.org/show_bug.cgi?id=21767)) diff --git a/debian/changelog b/debian/changelog index 2f36e48a..d0c0140c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,5 +1,6 @@ wine-compholio (1.7.31) UNRELEASED; urgency=low * Added possibility to temporarily disable patches to patch system. + * Added patch to allow selecting specific audio device for PulseAudio backend. * Removed patch for iphlpapi stub functions (accepted upstream). * Removed first part of patches for FindFirstFileExW (accepted upstream). -- Sebastian Lackner Mon, 03 Nov 2014 20:10:04 +0100 diff --git a/patches/Makefile b/patches/Makefile index 5485b9af..491122fe 100644 --- a/patches/Makefile +++ b/patches/Makefile @@ -1622,6 +1622,7 @@ winemenubuilder-Desktop_Icon_Path.ok: # | Included patches: # | * Winepulse patches extracted from https://launchpad.net/~ubuntu- # | wine/+archive/ubuntu/ppa/+files/wine1.7_1.7.22-0ubuntu1.debian.tar.gz. [rev 4, by Maarten Lankhorst] +# | * Expose audio devices directly to programs. [by Mark Harmstone] # | # | This patchset fixes the following Wine bugs: # | * [#10495] Support for PulseAudio backend for audio @@ -1659,8 +1660,10 @@ winepulse-PulseAudio_Support.ok: $(call APPLY_FILE,winepulse-PulseAudio_Support/0025-winepulse-use-a-pi-mutex-for-serialization.patch) $(call APPLY_FILE,winepulse-PulseAudio_Support/0026-winepulse-add-support-for-IMarshal.patch) $(call APPLY_FILE,winepulse-PulseAudio_Support/0027-winepulse-handle-stream-create-failing-correctly.patch) + $(call APPLY_FILE,winepulse-PulseAudio_Support/0028-winepulse-expose-audio-devices-directly-to-programs.patch) @( \ echo '+ { "winepulse-PulseAudio_Support", "Maarten Lankhorst", "Winepulse patches extracted from https://launchpad.net/~ubuntu-wine/+archive/ubuntu/ppa/+files/wine1.7_1.7.22-0ubuntu1.debian.tar.gz. [rev 4]" },'; \ + echo '+ { "winepulse-PulseAudio_Support", "Mark Harmstone", "Expose audio devices directly to programs." },'; \ ) > winepulse-PulseAudio_Support.ok # Patchset winex11-CandidateWindowPos diff --git a/patches/winepulse-PulseAudio_Support/0028-winepulse-expose-audio-devices-directly-to-programs.patch b/patches/winepulse-PulseAudio_Support/0028-winepulse-expose-audio-devices-directly-to-programs.patch new file mode 100644 index 00000000..76fab01a --- /dev/null +++ b/patches/winepulse-PulseAudio_Support/0028-winepulse-expose-audio-devices-directly-to-programs.patch @@ -0,0 +1,329 @@ +From daf353964820e0c9c3b49a5dab3eb952f27d27c3 Mon Sep 17 00:00:00 2001 +From: Mark Harmstone +Date: Mon, 3 Nov 2014 02:06:40 +0000 +Subject: winepulse: expose audio devices directly to programs + +At present, winepulse only exposes one input device and one output device. This +patch adds support for individual audio devices, allowing (among other things) +the same program to record from two devices at the same time. It also brings +winepulse more in line with both winealsa et al. and Windows itself. The +moveable "Pulseaudio" devices are still present, and should presumably be +used by default. + +Changes by Sebastian Lackner : +* Merge functions set_device_guid and get_device_guid as they are always used together +* Fixed compiler warnings with -Werror +* Some style fixes and better error handling +--- + dlls/winepulse.drv/mmdevdrv.c | 227 ++++++++++++++++++++++++++++++++++++++++-- + 1 file changed, 220 insertions(+), 7 deletions(-) + +diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c +index e755e8a..f6688d8 100644 +--- a/dlls/winepulse.drv/mmdevdrv.c ++++ b/dlls/winepulse.drv/mmdevdrv.c +@@ -85,6 +85,11 @@ const WCHAR pulse_keyW[] = {'S','o','f','t','w','a','r','e','\\', + 'W','i','n','e','\\','P','u','l','s','e',0}; + const WCHAR pulse_streamW[] = { 'S','t','r','e','a','m','V','o','l',0 }; + ++static const WCHAR drv_key_devicesW[] = {'S','o','f','t','w','a','r','e','\\', ++ 'W','i','n','e','\\','D','r','i','v','e','r','s','\\', ++ 'w','i','n','e','p','u','l','s','e','.','d','r','v','\\','D','e','v','i','c','e','s',0}; ++static const WCHAR guidW[] = {'g','u','i','d',0}; ++ + static GUID pulse_render_guid = + { 0xfd47d9cc, 0x4218, 0x4135, { 0x9c, 0xe2, 0x0c, 0x19, 0x5c, 0x87, 0x40, 0x5b } }; + static GUID pulse_capture_guid = +@@ -173,6 +178,7 @@ struct ACImpl { + IMMDevice *parent; + struct list entry; + float vol[PA_CHANNELS_MAX]; ++ char device[256]; + + LONG ref; + EDataFlow dataflow; +@@ -692,6 +698,9 @@ static HRESULT pulse_stream_connect(ACImpl *This, UINT32 period_bytes) { + char buffer[64]; + static LONG number; + pa_buffer_attr attr; ++ int moving = 0; ++ const char *dev = NULL; ++ + if (This->stream) { + pa_stream_disconnect(This->stream); + while (pa_stream_get_state(This->stream) == PA_STREAM_READY) +@@ -716,12 +725,21 @@ static HRESULT pulse_stream_connect(ACImpl *This, UINT32 period_bytes) { + attr.maxlength = attr.tlength = This->bufsize_bytes; + attr.prebuf = pa_frame_size(&This->ss); + dump_attr(&attr); ++ ++ /* If device name is given use exactly the specified device */ ++ if (This->device[0]){ ++ moving = PA_STREAM_DONT_MOVE; ++ dev = This->device; ++ } ++ + if (This->dataflow == eRender) +- ret = pa_stream_connect_playback(This->stream, NULL, &attr, +- PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_AUTO_TIMING_UPDATE|PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_EARLY_REQUESTS, NULL, NULL); ++ ret = pa_stream_connect_playback(This->stream, dev, &attr, ++ PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_AUTO_TIMING_UPDATE| ++ PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_EARLY_REQUESTS|moving, NULL, NULL); + else +- ret = pa_stream_connect_record(This->stream, NULL, &attr, +- PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_AUTO_TIMING_UPDATE|PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_EARLY_REQUESTS); ++ ret = pa_stream_connect_record(This->stream, dev, &attr, ++ PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_AUTO_TIMING_UPDATE| ++ PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_EARLY_REQUESTS|moving); + if (ret < 0) { + WARN("Returns %i\n", ret); + return AUDCLNT_E_ENDPOINT_CREATE_FAILED; +@@ -740,11 +758,127 @@ static HRESULT pulse_stream_connect(ACImpl *This, UINT32 period_bytes) { + return S_OK; + } + +-HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, const WCHAR ***ids, GUID **keys, ++static BOOL get_device_guid(EDataFlow flow, const char *device, GUID *guid) ++{ ++ HKEY key, dev_key; ++ DWORD type, size = sizeof(*guid); ++ WCHAR key_name[258]; ++ ++ key_name[0] = (flow == eCapture) ? '1' : '0'; ++ key_name[1] = ','; ++ if (!MultiByteToWideChar(CP_UTF8, 0, device, -1, key_name + 2, ++ (sizeof(key_name) / sizeof(*key_name)) - 2)) ++ return FALSE; ++ ++ if (RegCreateKeyExW(HKEY_CURRENT_USER, drv_key_devicesW, 0, NULL, 0, ++ KEY_WRITE|KEY_READ, NULL, &key, NULL) != ERROR_SUCCESS){ ++ ERR("Failed to open registry key %s\n", debugstr_w(drv_key_devicesW)); ++ return FALSE; ++ } ++ ++ if (RegCreateKeyExW(key, key_name, 0, NULL, 0, KEY_WRITE|KEY_READ, ++ NULL, &dev_key, NULL) != ERROR_SUCCESS){ ++ ERR("Failed to open registry key for device %s\n", debugstr_w(key_name)); ++ RegCloseKey(key); ++ return FALSE; ++ } ++ ++ if (RegQueryValueExW(dev_key, guidW, 0, &type, (BYTE *)guid, ++ &size) == ERROR_SUCCESS){ ++ if (type == REG_BINARY && size == sizeof(*guid)){ ++ RegCloseKey(dev_key); ++ RegCloseKey(key); ++ return TRUE; ++ } ++ ++ ERR("Invalid type for device %s GUID: %u; ignoring and overwriting\n", ++ wine_dbgstr_w(key_name), type); ++ } ++ ++ /* generate new GUID for this device */ ++ CoCreateGuid(guid); ++ ++ if (RegSetValueExW(dev_key, guidW, 0, REG_BINARY, (BYTE *)guid, ++ sizeof(GUID)) != ERROR_SUCCESS) ++ ERR("Failed to store device GUID for %s to registry\n", device); ++ ++ RegCloseKey(dev_key); ++ RegCloseKey(key); ++ return TRUE; ++} ++ ++struct pulse_all_info_cb_data { ++ EDataFlow flow; ++ WCHAR **ids; ++ GUID *keys; ++ UINT num; ++}; ++ ++static void pulse_all_sink_info_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { ++ struct pulse_all_info_cb_data *st = (struct pulse_all_info_cb_data*)userdata; ++ void *tmp; ++ DWORD len; ++ ++ if (!i) ++ return; ++ ++ tmp = HeapReAlloc(GetProcessHeap(), 0, st->ids, sizeof(WCHAR*) * (st->num + 1)); ++ if (!tmp) return; ++ st->ids = tmp; ++ ++ tmp = HeapReAlloc(GetProcessHeap(), 0, st->keys, sizeof(GUID) * (st->num + 1)); ++ if (!tmp) return; ++ st->keys = tmp; ++ ++ len = MultiByteToWideChar(CP_UTF8, 0, i->description, -1, NULL, 0); ++ if (!len) return; ++ ++ st->ids[st->num] = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)); ++ if (!st->ids[st->num]) return; ++ ++ MultiByteToWideChar(CP_UTF8, 0, i->description, -1, st->ids[st->num], len); ++ if (!get_device_guid(st->flow, i->name, &(st->keys[st->num]))) ++ CoCreateGuid(&(st->keys[st->num])); ++ ++ st->num++; ++} ++ ++static void pulse_all_source_info_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { ++ struct pulse_all_info_cb_data *st = (struct pulse_all_info_cb_data*)userdata; ++ void *tmp; ++ DWORD len; ++ ++ if (!i) ++ return; ++ ++ tmp = HeapReAlloc(GetProcessHeap(), 0, st->ids, sizeof(WCHAR*) * (st->num + 1)); ++ if (!tmp) return; ++ st->ids = tmp; ++ ++ tmp = HeapReAlloc(GetProcessHeap(), 0, st->keys, sizeof(GUID) * (st->num + 1)); ++ if (!tmp) return; ++ st->keys = tmp; ++ ++ len = MultiByteToWideChar(CP_UTF8, 0, i->description, -1, NULL, 0); ++ if (!len) return; ++ ++ st->ids[st->num] = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)); ++ if (!st->ids[st->num]) return; ++ ++ MultiByteToWideChar(CP_UTF8, 0, i->description, -1, st->ids[st->num], len); ++ if (!get_device_guid(st->flow, i->name, &(st->keys[st->num]))) ++ CoCreateGuid(&(st->keys[st->num])); ++ ++ st->num++; ++} ++ ++HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, WCHAR ***ids, GUID **keys, + UINT *num, UINT *def_index) + { + HRESULT hr = S_OK; + WCHAR *id; ++ pa_operation* o; ++ struct pulse_all_info_cb_data st; + + TRACE("%d %p %p %p\n", flow, ids, num, def_index); + +@@ -778,6 +912,25 @@ HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, const WCHAR ***ids, GUID ** + else + (*keys)[0] = pulse_capture_guid; + ++ st.flow = flow; ++ st.ids = *ids; ++ st.keys = *keys; ++ st.num = *num; ++ ++ if (flow == eRender) ++ o = pa_context_get_sink_info_list(pulse_ctx, &pulse_all_sink_info_cb, &st); ++ else ++ o = pa_context_get_source_info_list(pulse_ctx, &pulse_all_source_info_cb, &st); ++ ++ if (o){ ++ while (pa_operation_get_state(o) == PA_OPERATION_RUNNING){ } ++ pa_operation_unref(o); ++ } ++ ++ *ids = st.ids; ++ *keys = st.keys; ++ *num = st.num; ++ + return S_OK; + } + +@@ -794,8 +947,66 @@ int WINAPI AUDDRV_GetPriority(void) + return SUCCEEDED(hr) ? 3 : 0; + } + ++static BOOL get_pulse_name_by_guid(const GUID *guid, char *name, DWORD name_size, EDataFlow *flow) ++{ ++ HKEY key; ++ DWORD index = 0; ++ WCHAR key_name[258]; ++ DWORD key_name_size; ++ ++ if (RegOpenKeyExW(HKEY_CURRENT_USER, drv_key_devicesW, 0, KEY_READ, ++ &key) != ERROR_SUCCESS){ ++ ERR("No devices found in registry?\n"); ++ return FALSE; ++ } ++ ++ while(1){ ++ HKEY dev_key; ++ DWORD size, type; ++ GUID reg_guid; ++ ++ key_name_size = sizeof(key_name)/sizeof(WCHAR); ++ if(RegEnumKeyExW(key, index++, key_name, &key_name_size, NULL, ++ NULL, NULL, NULL) != ERROR_SUCCESS) ++ break; ++ ++ if (RegOpenKeyExW(key, key_name, 0, KEY_READ, &dev_key) != ERROR_SUCCESS){ ++ ERR("Couldn't open key: %s\n", wine_dbgstr_w(key_name)); ++ continue; ++ } ++ ++ size = sizeof(reg_guid); ++ if (RegQueryValueExW(dev_key, guidW, 0, &type, (BYTE *)®_guid, &size) == ERROR_SUCCESS){ ++ if (type == REG_BINARY && size == sizeof(reg_guid) && IsEqualGUID(®_guid, guid)){ ++ RegCloseKey(dev_key); ++ RegCloseKey(key); ++ ++ TRACE("Found matching device key: %s\n", wine_dbgstr_w(key_name)); ++ ++ if (key_name[0] == '0') ++ *flow = eRender; ++ else if (key_name[0] == '1') ++ *flow = eCapture; ++ else{ ++ ERR("Unknown device type: %c\n", key_name[0]); ++ return FALSE; ++ } ++ ++ return WideCharToMultiByte(CP_UNIXCP, 0, key_name + 2, -1, name, name_size, NULL, NULL); ++ } ++ } ++ ++ RegCloseKey(dev_key); ++ } ++ ++ RegCloseKey(key); ++ WARN("No matching device in registry for GUID %s\n", debugstr_guid(guid)); ++ return FALSE; ++} ++ + HRESULT WINAPI AUDDRV_GetAudioEndpoint(GUID *guid, IMMDevice *dev, IAudioClient **out) + { ++ char pulse_name[256] = {0}; + HRESULT hr; + ACImpl *This; + int i; +@@ -813,12 +1024,13 @@ HRESULT WINAPI AUDDRV_GetAudioEndpoint(GUID *guid, IMMDevice *dev, IAudioClient + } + + TRACE("%s %p %p\n", debugstr_guid(guid), dev, out); ++ + if (IsEqualGUID(guid, &pulse_render_guid)) + dataflow = eRender; + else if (IsEqualGUID(guid, &pulse_capture_guid)) + dataflow = eCapture; +- else +- return E_UNEXPECTED; ++ else if(!get_pulse_name_by_guid(guid, pulse_name, sizeof(pulse_name), &dataflow)) ++ return AUDCLNT_E_DEVICE_INVALIDATED; + + *out = NULL; + pthread_mutex_lock(&pulse_lock); +@@ -841,6 +1053,7 @@ HRESULT WINAPI AUDDRV_GetAudioEndpoint(GUID *guid, IMMDevice *dev, IAudioClient + This->parent = dev; + for (i = 0; i < PA_CHANNELS_MAX; ++i) + This->vol[i] = 1.f; ++ strcpy(This->device, pulse_name); + + hr = CoCreateFreeThreadedMarshaler((IUnknown*)This, &This->marshal); + if (hr) { +-- +2.1.3 + diff --git a/patches/winepulse-PulseAudio_Support/definition b/patches/winepulse-PulseAudio_Support/definition index 12638758..97f5bb12 100644 --- a/patches/winepulse-PulseAudio_Support/definition +++ b/patches/winepulse-PulseAudio_Support/definition @@ -2,3 +2,8 @@ Author: Maarten Lankhorst Subject: Winepulse patches extracted from https://launchpad.net/~ubuntu-wine/+archive/ubuntu/ppa/+files/wine1.7_1.7.22-0ubuntu1.debian.tar.gz. Revision: 4 Fixes: [10495] Support for PulseAudio backend for audio + +Author: Mark Harmstone +Subject: Expose audio devices directly to programs. +Revision: 1 +Fixes: Allow selection of audio device for PulseAudio backend