diff --git a/EarTrumpet.Interop/AudioDeviceService.cpp b/EarTrumpet.Interop/AudioDeviceService.cpp index ea3df7209..8b8d2b13c 100644 --- a/EarTrumpet.Interop/AudioDeviceService.cpp +++ b/EarTrumpet.Interop/AudioDeviceService.cpp @@ -1,8 +1,8 @@ #include "common.h" #include "Mmdeviceapi.h" +#include "PolicyConfig.h" #include "AudioDeviceService.h" #include "Functiondiscoverykeys_devpkey.h" -#include "PolicyConfig.h" #include "Propidl.h" #include "Endpointvolume.h" @@ -82,10 +82,25 @@ HRESULT AudioDeviceService::RefreshAudioDevices() HRESULT AudioDeviceService::SetDefaultAudioDevice(LPWSTR deviceId) { CComPtr policyConfig; - FAST_FAIL(CoCreateInstance(__uuidof(CPolicyConfigClient), nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&policyConfig))); + FAST_FAIL(GetPolicyConfigClient(&policyConfig)); return policyConfig->SetDefaultEndpoint(deviceId, ERole::eMultimedia); } +HRESULT AudioDeviceService::GetPolicyConfigClient(IPolicyConfig** client) +{ + // + // The IPolicyConfig interface GUID changed multiple times between Windows 10 TH1 and RS1 + // breaking app compat along the way. We attempt CoCreateInstance twice with known valid GUIDs + // to cover all Windows 10 scenarios. + // + if (FAILED(CoCreateInstance(CLSID_PolicyConfigClient, nullptr, CLSCTX_INPROC, IID_IPolicyConfig_TH2, reinterpret_cast(client)))) + { + FAST_FAIL(CoCreateInstance(CLSID_PolicyConfigClient, nullptr, CLSCTX_INPROC, IID_IPolicyConfig_TH1, reinterpret_cast(client))); + } + + return S_OK; +} + HRESULT AudioDeviceService::GetDeviceByDeviceId(PWSTR deviceId, IMMDevice** device) { CComPtr deviceEnumerator; diff --git a/EarTrumpet.Interop/AudioDeviceService.h b/EarTrumpet.Interop/AudioDeviceService.h index 103076649..8e8af3be9 100644 --- a/EarTrumpet.Interop/AudioDeviceService.h +++ b/EarTrumpet.Interop/AudioDeviceService.h @@ -21,6 +21,7 @@ namespace EarTrumpet void CleanUpAudioDevices(); HRESULT GetDeviceByDeviceId(PWSTR deviceId, IMMDevice** device); HRESULT SetMuteBoolForDevice(LPWSTR deviceId, BOOL value); + HRESULT GetPolicyConfigClient(IPolicyConfig** client); public: static AudioDeviceService* instance() diff --git a/EarTrumpet.Interop/AudioSessionService.cpp b/EarTrumpet.Interop/AudioSessionService.cpp index 77bcc875b..39264c73e 100644 --- a/EarTrumpet.Interop/AudioSessionService.cpp +++ b/EarTrumpet.Interop/AudioSessionService.cpp @@ -89,7 +89,7 @@ HRESULT AudioSessionService::CreateEtAudioSessionFromAudioSession(CComPtrGetGroupingParam(&etAudioSession->GroupingId)); CComHeapPtr sessionIdString; - FAST_FAIL(audioSessionControl2->GetSessionIdentifier(&sessionIdString)); + FAST_FAIL(audioSessionControl2->GetSessionInstanceIdentifier(&sessionIdString)); hash stringHash; etAudioSession->SessionId = stringHash(static_cast(sessionIdString)); @@ -100,9 +100,9 @@ HRESULT AudioSessionService::CreateEtAudioSessionFromAudioSession(CComPtrQueryInterface(IID_PPV_ARGS(&simpleAudioVolume))); FAST_FAIL(simpleAudioVolume->GetMasterVolume(&etAudioSession->Volume)); - BOOL isMuted; - FAST_FAIL(simpleAudioVolume->GetMute(&isMuted)); - etAudioSession->IsMuted = !!isMuted; + BOOL isMuted; + FAST_FAIL(simpleAudioVolume->GetMute(&isMuted)); + etAudioSession->IsMuted = !!isMuted; HRESULT hr = IsImmersiveProcess(pid); if (hr == S_OK) @@ -275,16 +275,16 @@ HRESULT AudioSessionService::SetAudioSessionVolume(unsigned long sessionId, floa HRESULT AudioSessionService::SetAudioSessionMute(unsigned long sessionId, bool isMuted) { - if (!_sessionMap[sessionId]) - { - return E_INVALIDARG; - } + if (!_sessionMap[sessionId]) + { + return E_INVALIDARG; + } - CComPtr simpleAudioVolume; - FAST_FAIL(_sessionMap[sessionId]->QueryInterface(IID_PPV_ARGS(&simpleAudioVolume))); + CComPtr simpleAudioVolume; + FAST_FAIL(_sessionMap[sessionId]->QueryInterface(IID_PPV_ARGS(&simpleAudioVolume))); - FAST_FAIL(simpleAudioVolume->SetMute(isMuted, nullptr)); - return S_OK; + FAST_FAIL(simpleAudioVolume->SetMute(isMuted, nullptr)); + return S_OK; } HRESULT AudioSessionService::GetAppProperties(PCWSTR pszAppId, PWSTR* ppszName, PWSTR* ppszIcon, ULONG *background) diff --git a/EarTrumpet.Interop/PolicyConfig.h b/EarTrumpet.Interop/PolicyConfig.h index 5135069c5..885afe9cf 100644 --- a/EarTrumpet.Interop/PolicyConfig.h +++ b/EarTrumpet.Interop/PolicyConfig.h @@ -1,7 +1,13 @@ #pragma once -interface DECLSPEC_UUID("f8679f50-850a-41cf-9c72-430f290290c8") IPolicyConfig; -class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient; +// 870AF99C-171D-4F9E-AF0D-E63DF40C2BC9 +const CLSID CLSID_PolicyConfigClient = { 0x870AF99C, 0x171D, 0x4F9E, { 0xAF, 0x0D, 0xE6, 0x3D, 0xF4, 0x0C, 0x2B, 0xC9 } }; + +// CA286FC3-91FD-42C3-8E9B-CAAFA66242E3 +const GUID IID_IPolicyConfig_TH1 = { 0xCA286FC3, 0x91FD, 0x42C3, { 0x8E, 0x9B, 0xCA, 0xAF, 0xA6, 0x62, 0x42, 0xE3 } }; + +// 6BE54BE8-A068-4875-A49D-0C2966473B11 +const GUID IID_IPolicyConfig_TH2 = { 0x6BE54BE8, 0xA068, 0x4875, { 0xA4, 0x9D, 0x0C, 0x29, 0x66, 0x47, 0x3B, 0x11 } }; interface IPolicyConfig : public IUnknown { diff --git a/EarTrumpet.Interop/exports.cpp b/EarTrumpet.Interop/exports.cpp index 6cb057e8b..28e61b8c6 100644 --- a/EarTrumpet.Interop/exports.cpp +++ b/EarTrumpet.Interop/exports.cpp @@ -1,6 +1,7 @@ #include "common.h" #include #include +#include "PolicyConfig.h" #include "AudioSessionService.h" #include "AudioDeviceService.h" diff --git a/EarTrumpet/MainWindow.xaml b/EarTrumpet/MainWindow.xaml index 82dae45fa..8fdaabe82 100644 --- a/EarTrumpet/MainWindow.xaml +++ b/EarTrumpet/MainWindow.xaml @@ -161,182 +161,181 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + - + HorizontalAlignment="Center" + FontSize="{DynamicResource {x:Static SystemFonts.MessageFontSizeKey}}" + TextWrapping="Wrap" /> + + + + + + + + + + + + + + - - - - - - - - - - - + + + - - + TextAlignment="Center" + Opacity="{Binding IsMuted, Converter={StaticResource opacityConverter}}" + Margin="0,-3,0,0" /> + + + + + + + + + + + + + + + - - - + + + - + - - - - - - - - - - + Opacity="{Binding IsMuted, Converter={StaticResource opacityConverter}}" + Margin="0,-3,0,0" /> + + + + + diff --git a/EarTrumpet/Properties/AssemblyInfo.cs b/EarTrumpet/Properties/AssemblyInfo.cs index 7dc0e525c..46ff9dc34 100644 --- a/EarTrumpet/Properties/AssemblyInfo.cs +++ b/EarTrumpet/Properties/AssemblyInfo.cs @@ -5,12 +5,12 @@ [assembly: AssemblyTitle("Ear Trumpet")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] +[assembly: AssemblyCompany("File-New-Project")] [assembly: AssemblyProduct("Ear Trumpet")] [assembly: AssemblyCopyright("")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("1.3.0.0")] -[assembly: AssemblyFileVersion("1.3.0.0")] +[assembly: AssemblyVersion("1.3.1.0")] +[assembly: AssemblyFileVersion("1.3.1.0")] [assembly: ComVisible(false)] [assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)] diff --git a/EarTrumpet/Properties/Resources.Designer.cs b/EarTrumpet/Properties/Resources.Designer.cs index 87b1c9424..efb968806 100644 --- a/EarTrumpet/Properties/Resources.Designer.cs +++ b/EarTrumpet/Properties/Resources.Designer.cs @@ -78,6 +78,15 @@ public class Resources { } } + /// + /// Looks up a localized string similar to No devices found. + /// + public static string ContextMenuNoDevices { + get { + return ResourceManager.GetString("ContextMenuNoDevices", resourceCulture); + } + } + /// /// Looks up a localized string similar to Show Desktop Apps. /// @@ -96,6 +105,15 @@ public class Resources { } } + /// + /// Looks up a localized string similar to It doesn't look like you have any audio devices.. + /// + public static string NoDevicesPanelContent { + get { + return ResourceManager.GetString("NoDevicesPanelContent", resourceCulture); + } + } + /// /// Looks up a localized string similar to Speech Runtime. /// diff --git a/EarTrumpet/Properties/Resources.de.resx b/EarTrumpet/Properties/Resources.de.resx index a7719db08..5349506f3 100644 --- a/EarTrumpet/Properties/Resources.de.resx +++ b/EarTrumpet/Properties/Resources.de.resx @@ -123,12 +123,18 @@ Beenden + + No devices found + Desktop Apps anzeigen Es sieht so aus als sind keine Apps geöffnet + + It doesn't look like you have any audio devices. + Speech Runtime diff --git a/EarTrumpet/Properties/Resources.fr.resx b/EarTrumpet/Properties/Resources.fr.resx index 3ee44ecda..0d65191cd 100644 --- a/EarTrumpet/Properties/Resources.fr.resx +++ b/EarTrumpet/Properties/Resources.fr.resx @@ -123,12 +123,18 @@ Quitter + + No devices found + Afficher les applications classiques du bureau Il semble que vous n'ayez aucune application ouverte. + + It doesn't look like you have any audio devices. + Speech Runtime diff --git a/EarTrumpet/Properties/Resources.nb-no.resx b/EarTrumpet/Properties/Resources.nb-no.resx index a8e75f370..bea5de70c 100644 --- a/EarTrumpet/Properties/Resources.nb-no.resx +++ b/EarTrumpet/Properties/Resources.nb-no.resx @@ -123,12 +123,18 @@ Avslutt + + No devices found + Vis skrivebordsapplikasjoner Det ser ikke ut som du har noen applikasjoner åpne. + + It doesn't look like you have any audio devices. + Speech Runtime diff --git a/EarTrumpet/Properties/Resources.nn-no.resx b/EarTrumpet/Properties/Resources.nn-no.resx index 9640d9194..3d6c567f5 100644 --- a/EarTrumpet/Properties/Resources.nn-no.resx +++ b/EarTrumpet/Properties/Resources.nn-no.resx @@ -123,12 +123,18 @@ Avslutt + + No devices found + Vis skrivebordsapplikasjonar Det ser ikkje ut som du har nokon applikasjonar opne. + + It doesn't look like you have any audio devices. + Speech Runtime diff --git a/EarTrumpet/Properties/Resources.resx b/EarTrumpet/Properties/Resources.resx index 57cbd0fdd..5fc0f064b 100644 --- a/EarTrumpet/Properties/Resources.resx +++ b/EarTrumpet/Properties/Resources.resx @@ -123,12 +123,18 @@ Exit + + No devices found + Show Desktop Apps It doesn't look like you have any apps open. + + It doesn't look like you have any audio devices. + Speech Runtime diff --git a/EarTrumpet/Services/TaskbarService.cs b/EarTrumpet/Services/TaskbarService.cs index cebe8d351..f72f6760f 100644 --- a/EarTrumpet/Services/TaskbarService.cs +++ b/EarTrumpet/Services/TaskbarService.cs @@ -14,14 +14,25 @@ public static TaskbarState GetWinTaskbarState() { APPBARDATA ABD = new APPBARDATA(); TaskbarState retState = new TaskbarState(); + var hwnd = User32.FindWindow(ClassName, null); ABD.cbSize = Marshal.SizeOf(ABD); ABD.uEdge = 0; - ABD.hWnd = User32.FindWindow(ClassName, null); + ABD.hWnd = hwnd; ABD.lParam = 1; - var tsize = Shell32.SHAppBarMessage((int)ABMsg.ABM_GETTASKBARPOS, ref ABD); - retState.TaskbarSize = ABD.rc; + RECT scaledTaskbarRect; + User32.GetWindowRect(hwnd, out scaledTaskbarRect); + + var taskbarNonDPIAwareSize = Shell32.SHAppBarMessage((int)ABMsg.ABM_GETTASKBARPOS, ref ABD); + + var scalingAmount = (double)(scaledTaskbarRect.bottom - scaledTaskbarRect.top) / (ABD.rc.bottom - ABD.rc.top); + + retState.TaskbarSize = default(RECT); + retState.TaskbarSize.top = (int)(ABD.rc.top * scalingAmount); + retState.TaskbarSize.bottom = (int)(ABD.rc.bottom * scalingAmount); + retState.TaskbarSize.left = (int)(ABD.rc.left * scalingAmount); + retState.TaskbarSize.right = (int)(ABD.rc.right * scalingAmount); var screen = Screen.AllScreens.FirstOrDefault(x => x.Bounds.Contains( new Rectangle( @@ -46,13 +57,16 @@ public static TaskbarState GetWinTaskbarState() } return retState; - } + } } public static class User32 { [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr FindWindow(string lpClassName, string lpWindowName); + + [DllImport("user32.dll", SetLastError = true)] + public static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect); } [StructLayout(LayoutKind.Sequential)] diff --git a/EarTrumpet/TrayIcon.cs b/EarTrumpet/TrayIcon.cs index e376e7245..556e4fe8e 100644 --- a/EarTrumpet/TrayIcon.cs +++ b/EarTrumpet/TrayIcon.cs @@ -84,6 +84,13 @@ private void SetupDeviceMenuItems() } var audioDevices = _audioDeviceService.GetAudioDevices().ToList(); + if (audioDevices.Count == 0) + { + var newItem = new System.Windows.Forms.MenuItem(EarTrumpet.Properties.Resources.ContextMenuNoDevices); + newItem.Name = $"{_deviceItemPrefix}"; + newItem.Enabled = false; + _trayIcon.ContextMenu.MenuItems.Add(0, newItem); + } for (int j = 0; j < audioDevices.Count; j++) { var device = audioDevices[j]; diff --git a/EarTrumpet/ViewModels/AudioMixerViewModel.cs b/EarTrumpet/ViewModels/AudioMixerViewModel.cs index 62b0aa6fa..2c74dbb3f 100644 --- a/EarTrumpet/ViewModels/AudioMixerViewModel.cs +++ b/EarTrumpet/ViewModels/AudioMixerViewModel.cs @@ -57,6 +57,9 @@ public void SetDeviceMute(EarTrumpetAudioDeviceModel device, bool isMuted) public Visibility ListVisibility { get; private set; } public Visibility NoAppsPaneVisibility { get; private set; } + public Visibility DeviceVisibility { get; private set; } + + public string NoItemsContent { get; private set; } private readonly EarTrumpetAudioSessionService _audioService; private readonly EarTrumpetAudioDeviceService _deviceService; @@ -72,25 +75,28 @@ public AudioMixerViewModel() public void Refresh() { - var defaultDevice = _deviceService.GetAudioDevices().FirstOrDefault(x => x.IsDefault); - var volume = _deviceService.GetAudioDeviceVolume(defaultDevice.Id); - var newDevice = new DeviceAppItemViewModel(_proxy, defaultDevice, volume); - if (Device != null && Device.IsSame(newDevice)) - { - Device.UpdateFromOther(newDevice); - } - else + var devices = _deviceService.GetAudioDevices(); + if (devices.Any()) { - Device = newDevice; + var defaultDevice = devices.FirstOrDefault(x => x.IsDefault); + var volume = _deviceService.GetAudioDeviceVolume(defaultDevice.Id); + var newDevice = new DeviceAppItemViewModel(_proxy, defaultDevice, volume); + if (Device != null && Device.IsSame(newDevice)) + { + Device.UpdateFromOther(newDevice); + } + else + { + Device = newDevice; + } + RaisePropertyChanged("Device"); } - RaisePropertyChanged("Device"); - + bool hasApps = Apps.Count > 0; - var sessions = _audioService.GetAudioSessionGroups().Select(x => new AppItemViewModel(_proxy, x)); List staleSessionsToRemove = new List(); - + // remove stale apps foreach (var app in Apps) { @@ -115,15 +121,16 @@ public void Refresh() findApp.UpdateFromOther(session); } } - - ListVisibility = Apps.Count > 0 ? Visibility.Visible : Visibility.Hidden; - NoAppsPaneVisibility = Apps.Count == 0 ? Visibility.Visible : Visibility.Hidden; - - if (hasApps != (Apps.Count > 0)) - { - RaisePropertyChanged("ListVisibility"); - RaisePropertyChanged("NoAppsPaneVisibility"); - } + + ListVisibility = Apps.Count > 0 ? Visibility.Visible : Visibility.Collapsed; + NoAppsPaneVisibility = Apps.Count == 0 ? Visibility.Visible : Visibility.Collapsed; + NoItemsContent = Device == null ? Properties.Resources.NoDevicesPanelContent : Properties.Resources.NoAppsPanelContent; + DeviceVisibility = Device != null ? Visibility.Visible : Visibility.Collapsed; + + RaisePropertyChanged("ListVisibility"); + RaisePropertyChanged("NoAppsPaneVisibility"); + RaisePropertyChanged("NoItemsContent"); + RaisePropertyChanged("DeviceVisibility"); } } }