diff --git a/.gitignore b/.gitignore index 2daf17078..0d62d3fb5 100644 --- a/.gitignore +++ b/.gitignore @@ -194,3 +194,5 @@ FakesAssemblies/ # Visual Studio 6 workspace options file *.opt +/EarTrumpet.VC.VC.opendb +/EarTrumpet.VC.db diff --git a/Build/Redists/vcredist_x86.exe b/Build/Redists/vcredist_x86.exe deleted file mode 100644 index b79557817..000000000 Binary files a/Build/Redists/vcredist_x86.exe and /dev/null differ diff --git a/EarTrumpet.Interop/AudioDeviceService.cpp b/EarTrumpet.Interop/AudioDeviceService.cpp new file mode 100644 index 000000000..ea3df7209 --- /dev/null +++ b/EarTrumpet.Interop/AudioDeviceService.cpp @@ -0,0 +1,154 @@ +#include "common.h" +#include "Mmdeviceapi.h" +#include "AudioDeviceService.h" +#include "Functiondiscoverykeys_devpkey.h" +#include "PolicyConfig.h" +#include "Propidl.h" +#include "Endpointvolume.h" + +using namespace std; +using namespace std::tr1; +using namespace EarTrumpet::Interop; + +AudioDeviceService* AudioDeviceService::__instance = nullptr; + +void AudioDeviceService::CleanUpAudioDevices() +{ + for (auto device = _devices.begin(); device != _devices.end(); device++) + { + CoTaskMemFree(device->Id); + CoTaskMemFree(device->DisplayName); + } + + _devices.clear(); +} + +HRESULT AudioDeviceService::RefreshAudioDevices() +{ + CleanUpAudioDevices(); + + CComPtr deviceEnumerator; + FAST_FAIL(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&deviceEnumerator))); + + CComPtr deviceCollection; + FAST_FAIL(deviceEnumerator->EnumAudioEndpoints(EDataFlow::eRender, ERole::eMultimedia, &deviceCollection)); + + CComPtr defaultDevice; + FAST_FAIL(deviceEnumerator->GetDefaultAudioEndpoint(EDataFlow::eRender, ERole::eMultimedia, &defaultDevice)); + + CComHeapPtr defaultDeviceId; + FAST_FAIL(defaultDevice->GetId(&defaultDeviceId)); + + UINT numDevices; + FAST_FAIL(deviceCollection->GetCount(&numDevices)); + + for (UINT i = 0; i < numDevices; i++) + { + CComPtr device; + if (FAILED(deviceCollection->Item(i, &device))) + { + continue; + } + + CComHeapPtr deviceId; + FAST_FAIL(device->GetId(&deviceId)); + + CComPtr propertyStore; + FAST_FAIL(device->OpenPropertyStore(STGM_READ, &propertyStore)); + + PROPVARIANT friendlyName; + PropVariantInit(&friendlyName); + FAST_FAIL(propertyStore->GetValue(PKEY_Device_FriendlyName, &friendlyName)); + + CComPtr audioEndpointVol; + FAST_FAIL(device->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC, nullptr, reinterpret_cast(&audioEndpointVol))); + + BOOL isMuted; + FAST_FAIL(audioEndpointVol->GetMute(&isMuted)); + + EarTrumpetAudioDevice audioDevice = {}; + FAST_FAIL(SHStrDup(friendlyName.pwszVal, &audioDevice.DisplayName)); + FAST_FAIL(SHStrDup(deviceId, &audioDevice.Id)); + audioDevice.IsDefault = (wcscmp(defaultDeviceId, deviceId) == 0); + audioDevice.IsMuted = !!isMuted; + _devices.push_back(audioDevice); + + PropVariantClear(&friendlyName); + } + + return S_OK; +} + +HRESULT AudioDeviceService::SetDefaultAudioDevice(LPWSTR deviceId) +{ + CComPtr policyConfig; + FAST_FAIL(CoCreateInstance(__uuidof(CPolicyConfigClient), nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&policyConfig))); + return policyConfig->SetDefaultEndpoint(deviceId, ERole::eMultimedia); +} + +HRESULT AudioDeviceService::GetDeviceByDeviceId(PWSTR deviceId, IMMDevice** device) +{ + CComPtr deviceEnumerator; + FAST_FAIL(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&deviceEnumerator))); + + return deviceEnumerator->GetDevice(deviceId, device); +} + +HRESULT AudioDeviceService::GetAudioDeviceVolume(LPWSTR deviceId, float* volume) +{ + CComPtr device; + FAST_FAIL(this->GetDeviceByDeviceId(deviceId, &device)); + + CComPtr audioEndpointVol; + FAST_FAIL(device->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC, nullptr, reinterpret_cast(&audioEndpointVol))); + + return audioEndpointVol->GetMasterVolumeLevelScalar(volume); +} + +HRESULT AudioDeviceService::SetAudioDeviceVolume(LPWSTR deviceId, float volume) +{ + CComPtr device; + FAST_FAIL(this->GetDeviceByDeviceId(deviceId, &device)); + + CComPtr audioEndpointVol; + FAST_FAIL(device->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC, nullptr, reinterpret_cast(&audioEndpointVol))); + + return audioEndpointVol->SetMasterVolumeLevelScalar(volume, nullptr); +} + +HRESULT AudioDeviceService::GetAudioDevices(void** audioDevices) +{ + if (_devices.size() == 0) + { + return HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS); + } + + *audioDevices = &_devices[0]; + return S_OK; +} + +HRESULT AudioDeviceService::SetMuteBoolForDevice(LPWSTR deviceId, BOOL value) +{ + CComPtr device; + FAST_FAIL(this->GetDeviceByDeviceId(deviceId, &device)); + + CComPtr audioEndpointVol; + FAST_FAIL(device->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC, nullptr, reinterpret_cast(&audioEndpointVol))); + + return audioEndpointVol->SetMute(value, nullptr); +} + +HRESULT AudioDeviceService::MuteAudioDevice(LPWSTR deviceId) +{ + return SetMuteBoolForDevice(deviceId, TRUE); +} + +HRESULT AudioDeviceService::UnmuteAudioDevice(LPWSTR deviceId) +{ + return SetMuteBoolForDevice(deviceId, FALSE); +} + +int AudioDeviceService::GetAudioDeviceCount() +{ + return _devices.size(); +} diff --git a/EarTrumpet.Interop/AudioDeviceService.h b/EarTrumpet.Interop/AudioDeviceService.h new file mode 100644 index 000000000..103076649 --- /dev/null +++ b/EarTrumpet.Interop/AudioDeviceService.h @@ -0,0 +1,45 @@ +#pragma once + +namespace EarTrumpet +{ + namespace Interop + { + struct EarTrumpetAudioDevice + { + LPWSTR Id; + LPWSTR DisplayName; + bool IsDefault; + bool IsMuted; + }; + + class AudioDeviceService + { + private: + static AudioDeviceService* __instance; + std::vector _devices; + + void CleanUpAudioDevices(); + HRESULT GetDeviceByDeviceId(PWSTR deviceId, IMMDevice** device); + HRESULT SetMuteBoolForDevice(LPWSTR deviceId, BOOL value); + + public: + static AudioDeviceService* instance() + { + if (!__instance) + { + __instance = new AudioDeviceService; + } + return __instance; + } + + HRESULT GetAudioDevices(void** audioDevices); + HRESULT GetAudioDeviceVolume(LPWSTR deviceId, float* volume); + HRESULT SetAudioDeviceVolume(LPWSTR deviceId, float volume); + HRESULT SetDefaultAudioDevice(LPWSTR deviceId); + HRESULT MuteAudioDevice(LPWSTR deviceId); + HRESULT UnmuteAudioDevice(LPWSTR deviceId); + HRESULT RefreshAudioDevices(); + int GetAudioDeviceCount(); + }; + } +} diff --git a/EarTrumpet.Interop/AudioSessionService.cpp b/EarTrumpet.Interop/AudioSessionService.cpp index d9632ae47..77bcc875b 100644 --- a/EarTrumpet.Interop/AudioSessionService.cpp +++ b/EarTrumpet.Interop/AudioSessionService.cpp @@ -1,310 +1,326 @@ -#include "common.h" -#include -#include -#include -#include -#include -#include -#include "AudioSessionService.h" -#include "ShellProperties.h" -#include "MrtResourceManager.h" - -using namespace std; -using namespace std::tr1; -using namespace EarTrumpet::Interop; - -AudioSessionService* AudioSessionService::__instance = nullptr; - -struct PackageInfoReferenceDeleter -{ - void operator()(PACKAGE_INFO_REFERENCE* reference) - { - ClosePackageInfo(*reference); - } -}; - -typedef unique_ptr PackageInfoReference; - -void AudioSessionService::CleanUpAudioSessions() -{ - for (auto session = _sessions.begin(); session != _sessions.end(); session++) - { - CoTaskMemFree(session->DisplayName); - CoTaskMemFree(session->IconPath); - } - - _sessions.clear(); - _sessionMap.clear(); -} - -int AudioSessionService::GetAudioSessionCount() -{ - return _sessions.size(); -} - -HRESULT AudioSessionService::RefreshAudioSessions() -{ - CleanUpAudioSessions(); - - CComPtr deviceEnumerator; - FAST_FAIL(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&deviceEnumerator))); - - // TIP: Role parameter is not actually used https://msdn.microsoft.com/en-us/library/windows/desktop/dd371401.aspx - CComPtr device; - FAST_FAIL(deviceEnumerator->GetDefaultAudioEndpoint(EDataFlow::eRender, ERole::eMultimedia, &device)); - - CComPtr audioSessionManager; - FAST_FAIL(device->Activate(__uuidof(IAudioSessionManager2), CLSCTX_INPROC, nullptr, (void**)&audioSessionManager)); - - CComPtr audioSessionEnumerator; - FAST_FAIL(audioSessionManager->GetSessionEnumerator(&audioSessionEnumerator)); - - int sessionCount; - FAST_FAIL(audioSessionEnumerator->GetCount(&sessionCount)); - - for (int i = 0; i < sessionCount; i++) - { - EarTrumpetAudioSession audioSession; - if (SUCCEEDED(CreateEtAudioSessionFromAudioSession(audioSessionEnumerator, i, &audioSession))) - { - _sessions.push_back(audioSession); - } - } - - return S_OK; -} - -HRESULT AudioSessionService::CreateEtAudioSessionFromAudioSession(CComPtr audioSessionEnumerator, int sessionCount, EarTrumpetAudioSession* etAudioSession) -{ - CComPtr audioSessionControl; - FAST_FAIL(audioSessionEnumerator->GetSession(sessionCount, &audioSessionControl)); - - CComPtr audioSessionControl2; - FAST_FAIL(audioSessionControl->QueryInterface(IID_PPV_ARGS(&audioSessionControl2))); - - DWORD pid; - FAST_FAIL(audioSessionControl2->GetProcessId(&pid)); - - etAudioSession->ProcessId = pid; - - FAST_FAIL(audioSessionControl2->GetGroupingParam(&etAudioSession->GroupingId)); - - CComHeapPtr sessionIdString; - FAST_FAIL(audioSessionControl2->GetSessionIdentifier(&sessionIdString)); - - hash stringHash; - etAudioSession->SessionId = stringHash(static_cast(sessionIdString)); - - _sessionMap[etAudioSession->SessionId] = audioSessionControl2; - - CComPtr simpleAudioVolume; - FAST_FAIL(audioSessionControl->QueryInterface(IID_PPV_ARGS(&simpleAudioVolume))); - FAST_FAIL(simpleAudioVolume->GetMasterVolume(&etAudioSession->Volume)); - - HRESULT hr = IsImmersiveProcess(pid); - if (hr == S_OK) - { - PWSTR appUserModelId; - FAST_FAIL(GetAppUserModelIdFromPid(pid, &appUserModelId)); - - FAST_FAIL(GetAppProperties(appUserModelId, &etAudioSession->DisplayName, &etAudioSession->IconPath, &etAudioSession->BackgroundColor)); - - etAudioSession->IsDesktopApp = false; - } - else if (hr == S_FALSE) - { - bool isSystemSoundsSession = (S_OK == audioSessionControl2->IsSystemSoundsSession()); - - AudioSessionState state; - FAST_FAIL(audioSessionControl2->GetState(&state)); - if (!isSystemSoundsSession && (state == AudioSessionState::AudioSessionStateExpired)) - { - return E_NOT_VALID_STATE; - } - - if (isSystemSoundsSession) - { - PCWSTR pszDllPath; - BOOL isWow64Process; - if (!IsWow64Process(GetCurrentProcess(), &isWow64Process) || isWow64Process) - { - pszDllPath = L"%windir%\\sysnative\\audiosrv.dll"; - } - else - { - pszDllPath = L"%windir%\\system32\\audiosrv.dll"; - } - - wchar_t szPath[MAX_PATH] = {}; - if (0 == ExpandEnvironmentStrings(pszDllPath, szPath, ARRAYSIZE(szPath))) - { - return E_FAIL; - } - - FAST_FAIL(SHStrDup(pszDllPath, &etAudioSession->IconPath)); - FAST_FAIL(SHStrDup(L"System Sounds", &etAudioSession->DisplayName)); - } - else - { - shared_ptr processHandle(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid), CloseHandle); - FAST_FAIL_HANDLE(processHandle.get()); - - wchar_t imagePath[MAX_PATH] = {}; - DWORD dwCch = ARRAYSIZE(imagePath); - FAST_FAIL(QueryFullProcessImageName(processHandle.get(), 0, imagePath, &dwCch) == 0 ? E_FAIL : S_OK); - - FAST_FAIL(SHStrDup(imagePath, &etAudioSession->IconPath)); - FAST_FAIL(SHStrDup(PathFindFileName(imagePath), &etAudioSession->DisplayName)); - } - - etAudioSession->IsDesktopApp = true; - etAudioSession->BackgroundColor = 0x00000000; - } - - return S_OK; -} - -HRESULT AudioSessionService::GetAudioSessions(void** audioSessions) -{ - if (_sessions.size() == 0) - { - return HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS); - } - - *audioSessions = &_sessions[0]; - return S_OK; -} - -HRESULT AudioSessionService::IsImmersiveProcess(DWORD pid) -{ - shared_ptr processHandle(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid), CloseHandle); - FAST_FAIL_HANDLE(processHandle.get()); - return (::IsImmersiveProcess(processHandle.get()) ? S_OK : S_FALSE); -} - -HRESULT AudioSessionService::CanResolveAppByApplicationUserModelId(LPCWSTR applicationUserModelId) -{ - CComPtr item; - return SUCCEEDED(SHCreateItemInKnownFolder(FOLDERID_AppsFolder, KF_FLAG_DONT_VERIFY, applicationUserModelId, IID_PPV_ARGS(&item))); -} - -HRESULT AudioSessionService::GetAppUserModelIdFromPid(DWORD pid, LPWSTR* applicationUserModelId) -{ - shared_ptr processHandle(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid), CloseHandle); - FAST_FAIL_HANDLE(processHandle.get()); - - unsigned int appUserModelIdLength = 0; - long returnCode = GetApplicationUserModelId(processHandle.get(), &appUserModelIdLength, nullptr); - if (returnCode != ERROR_INSUFFICIENT_BUFFER) - { - return HRESULT_FROM_WIN32(returnCode); - } - - unique_ptr appUserModelId(new wchar_t[appUserModelIdLength]); - returnCode = GetApplicationUserModelId(processHandle.get(), &appUserModelIdLength, appUserModelId.get()); - if (returnCode != ERROR_SUCCESS) - { - return HRESULT_FROM_WIN32(returnCode); - } - - if (CanResolveAppByApplicationUserModelId(appUserModelId.get())) - { - FAST_FAIL(SHStrDup(appUserModelId.get(), applicationUserModelId)); - } - else - { - wchar_t packageFamilyName[PACKAGE_FAMILY_NAME_MAX_LENGTH]; - UINT32 packageFamilyNameLength = ARRAYSIZE(packageFamilyName); - wchar_t packageRelativeAppId[PACKAGE_RELATIVE_APPLICATION_ID_MAX_LENGTH]; - UINT32 packageRelativeAppIdLength = ARRAYSIZE(packageRelativeAppId); - - FAST_FAIL_WIN32(ParseApplicationUserModelId(appUserModelId.get(), &packageFamilyNameLength, packageFamilyName, &packageRelativeAppIdLength, packageRelativeAppId)); - - UINT32 packageCount = 0; - UINT32 packageNamesBufferLength = 0; - FAST_FAIL_BUFFER(FindPackagesByPackageFamily(packageFamilyName, PACKAGE_FILTER_HEAD | PACKAGE_INFORMATION_BASIC, &packageCount, nullptr, &packageNamesBufferLength, nullptr, nullptr)); - - if (packageCount <= 0) - { - return E_NOTFOUND; - } - - unique_ptr packageNames(new PWSTR[packageCount]); - unique_ptr buffer(new wchar_t[packageNamesBufferLength]); - FAST_FAIL_WIN32(FindPackagesByPackageFamily(packageFamilyName, PACKAGE_FILTER_HEAD | PACKAGE_INFORMATION_BASIC, &packageCount, packageNames.get(), &packageNamesBufferLength, buffer.get(), nullptr)); - - PackageInfoReference packageInfoRef; - PACKAGE_INFO_REFERENCE rawPackageInfoRef; - FAST_FAIL_WIN32(OpenPackageInfoByFullName(packageNames[0], 0, &rawPackageInfoRef)); - packageInfoRef.reset(&rawPackageInfoRef); - - UINT32 packageIdsLength = 0; - UINT32 packageIdCount = 0; - FAST_FAIL_BUFFER(GetPackageApplicationIds(*packageInfoRef.get(), &packageIdsLength, nullptr, &packageIdCount)); - - if (packageIdCount <= 0) - { - return E_NOTFOUND; - } - - unique_ptr packageIdsRaw(new BYTE[packageIdsLength]); - FAST_FAIL_WIN32(GetPackageApplicationIds(*packageInfoRef.get(), &packageIdsLength, packageIdsRaw.get(), &packageIdCount)); - - PCWSTR* packageIds = reinterpret_cast(packageIdsRaw.get()); - FAST_FAIL(SHStrDup(packageIds[0], applicationUserModelId)); - } - - return S_OK; -} - -HRESULT AudioSessionService::SetAudioSessionVolume(unsigned long sessionId, float volume) -{ - if (!_sessionMap[sessionId]) - { - return E_INVALIDARG; - } - - CComPtr simpleAudioVolume; - FAST_FAIL(_sessionMap[sessionId]->QueryInterface(IID_PPV_ARGS(&simpleAudioVolume))); - - FAST_FAIL(simpleAudioVolume->SetMasterVolume(volume, nullptr)); - - return S_OK; -} - -HRESULT AudioSessionService::GetAppProperties(PCWSTR pszAppId, PWSTR* ppszName, PWSTR* ppszIcon, ULONG *background) -{ - *ppszIcon = nullptr; - *ppszName = nullptr; - *background = 0; - - CComPtr item; - FAST_FAIL(SHCreateItemInKnownFolder(FOLDERID_AppsFolder, KF_FLAG_DONT_VERIFY, pszAppId, IID_PPV_ARGS(&item))); - - CComHeapPtr itemName; - FAST_FAIL(item->GetString(PKEY_ItemNameDisplay, &itemName)); - FAST_FAIL(item->GetUInt32(PKEY_AppUserModel_Background, background)); - - CComHeapPtr installPath; - FAST_FAIL(item->GetString(PKEY_AppUserModel_PackageInstallPath, &installPath)); - - CComHeapPtr iconPath; - FAST_FAIL(item->GetString(PKEY_AppUserModel_Icon, &iconPath)); - - CComHeapPtr fullPackagePath; - FAST_FAIL(item->GetString(PKEY_AppUserModel_PackageFullName, &fullPackagePath)); - - CComPtr mrtResMgr; - FAST_FAIL(CoCreateInstance(__uuidof(MrtResourceManager), nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&mrtResMgr))); - FAST_FAIL(mrtResMgr->InitializeForPackage(fullPackagePath)); - +#include "common.h" +#include +#include +#include +#include +#include +#include +#include "AudioSessionService.h" +#include "ShellProperties.h" +#include "MrtResourceManager.h" + +using namespace std; +using namespace std::tr1; +using namespace EarTrumpet::Interop; + +AudioSessionService* AudioSessionService::__instance = nullptr; + +struct PackageInfoReferenceDeleter +{ + void operator()(PACKAGE_INFO_REFERENCE* reference) + { + ClosePackageInfo(*reference); + } +}; + +typedef unique_ptr PackageInfoReference; + +void AudioSessionService::CleanUpAudioSessions() +{ + for (auto session = _sessions.begin(); session != _sessions.end(); session++) + { + CoTaskMemFree(session->DisplayName); + CoTaskMemFree(session->IconPath); + } + + _sessions.clear(); + _sessionMap.clear(); +} + +int AudioSessionService::GetAudioSessionCount() +{ + return _sessions.size(); +} + +HRESULT AudioSessionService::RefreshAudioSessions() +{ + CleanUpAudioSessions(); + + CComPtr deviceEnumerator; + FAST_FAIL(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&deviceEnumerator))); + + CComPtr device; + FAST_FAIL(deviceEnumerator->GetDefaultAudioEndpoint(EDataFlow::eRender, ERole::eMultimedia, &device)); + + CComPtr audioSessionManager; + FAST_FAIL(device->Activate(__uuidof(IAudioSessionManager2), CLSCTX_INPROC, nullptr, (void**)&audioSessionManager)); + + CComPtr audioSessionEnumerator; + FAST_FAIL(audioSessionManager->GetSessionEnumerator(&audioSessionEnumerator)); + + int sessionCount; + FAST_FAIL(audioSessionEnumerator->GetCount(&sessionCount)); + + for (int i = 0; i < sessionCount; i++) + { + EarTrumpetAudioSession audioSession; + if (SUCCEEDED(CreateEtAudioSessionFromAudioSession(audioSessionEnumerator, i, &audioSession))) + { + _sessions.push_back(audioSession); + } + } + + return S_OK; +} + +HRESULT AudioSessionService::CreateEtAudioSessionFromAudioSession(CComPtr audioSessionEnumerator, int sessionCount, EarTrumpetAudioSession* etAudioSession) +{ + CComPtr audioSessionControl; + FAST_FAIL(audioSessionEnumerator->GetSession(sessionCount, &audioSessionControl)); + + CComPtr audioSessionControl2; + FAST_FAIL(audioSessionControl->QueryInterface(IID_PPV_ARGS(&audioSessionControl2))); + + DWORD pid; + FAST_FAIL(audioSessionControl2->GetProcessId(&pid)); + + etAudioSession->ProcessId = pid; + + FAST_FAIL(audioSessionControl2->GetGroupingParam(&etAudioSession->GroupingId)); + + CComHeapPtr sessionIdString; + FAST_FAIL(audioSessionControl2->GetSessionIdentifier(&sessionIdString)); + + hash stringHash; + etAudioSession->SessionId = stringHash(static_cast(sessionIdString)); + + _sessionMap[etAudioSession->SessionId] = audioSessionControl2; + + CComPtr simpleAudioVolume; + FAST_FAIL(audioSessionControl->QueryInterface(IID_PPV_ARGS(&simpleAudioVolume))); + FAST_FAIL(simpleAudioVolume->GetMasterVolume(&etAudioSession->Volume)); + + BOOL isMuted; + FAST_FAIL(simpleAudioVolume->GetMute(&isMuted)); + etAudioSession->IsMuted = !!isMuted; + + HRESULT hr = IsImmersiveProcess(pid); + if (hr == S_OK) + { + PWSTR appUserModelId; + FAST_FAIL(GetAppUserModelIdFromPid(pid, &appUserModelId)); + + FAST_FAIL(GetAppProperties(appUserModelId, &etAudioSession->DisplayName, &etAudioSession->IconPath, &etAudioSession->BackgroundColor)); + + etAudioSession->IsDesktopApp = false; + } + else if (hr == S_FALSE) + { + bool isSystemSoundsSession = (S_OK == audioSessionControl2->IsSystemSoundsSession()); + AudioSessionState state; + FAST_FAIL(audioSessionControl2->GetState(&state)); + if (!isSystemSoundsSession && (state == AudioSessionState::AudioSessionStateExpired)) + { + return E_NOT_VALID_STATE; + } + + if (isSystemSoundsSession) + { + PCWSTR pszDllPath; + BOOL isWow64Process; + if (!IsWow64Process(GetCurrentProcess(), &isWow64Process) || isWow64Process) + { + pszDllPath = L"%windir%\\sysnative\\audiosrv.dll"; + } + else + { + pszDllPath = L"%windir%\\system32\\audiosrv.dll"; + } + + wchar_t szPath[MAX_PATH] = {}; + if (0 == ExpandEnvironmentStrings(pszDllPath, szPath, ARRAYSIZE(szPath))) + { + return E_FAIL; + } + + FAST_FAIL(SHStrDup(pszDllPath, &etAudioSession->IconPath)); + FAST_FAIL(SHStrDup(L"System Sounds", &etAudioSession->DisplayName)); + } + else + { + shared_ptr processHandle(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid), CloseHandle); + FAST_FAIL_HANDLE(processHandle.get()); + + wchar_t imagePath[MAX_PATH] = {}; + DWORD dwCch = ARRAYSIZE(imagePath); + FAST_FAIL(QueryFullProcessImageName(processHandle.get(), 0, imagePath, &dwCch) == 0 ? E_FAIL : S_OK); + FAST_FAIL(SHStrDup(imagePath, &etAudioSession->IconPath)); + FAST_FAIL(SHStrDup(PathFindFileName(imagePath), &etAudioSession->DisplayName)); + } + + etAudioSession->IsDesktopApp = true; + etAudioSession->BackgroundColor = 0x00000000; + } + + return S_OK; +} + +HRESULT AudioSessionService::GetAudioSessions(void** audioSessions) +{ + if (_sessions.size() == 0) + { + return HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS); + } + + *audioSessions = &_sessions[0]; + return S_OK; +} + +HRESULT AudioSessionService::IsImmersiveProcess(DWORD pid) +{ + shared_ptr processHandle(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid), CloseHandle); + FAST_FAIL_HANDLE(processHandle.get()); + return (::IsImmersiveProcess(processHandle.get()) ? S_OK : S_FALSE); +} + +HRESULT AudioSessionService::CanResolveAppByApplicationUserModelId(LPCWSTR applicationUserModelId) +{ + CComPtr item; + return SUCCEEDED(SHCreateItemInKnownFolder(FOLDERID_AppsFolder, KF_FLAG_DONT_VERIFY, applicationUserModelId, IID_PPV_ARGS(&item))); +} + +HRESULT AudioSessionService::GetAppUserModelIdFromPid(DWORD pid, LPWSTR* applicationUserModelId) +{ + shared_ptr processHandle(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid), CloseHandle); + FAST_FAIL_HANDLE(processHandle.get()); + + unsigned int appUserModelIdLength = 0; + long returnCode = GetApplicationUserModelId(processHandle.get(), &appUserModelIdLength, nullptr); + if (returnCode != ERROR_INSUFFICIENT_BUFFER) + { + return HRESULT_FROM_WIN32(returnCode); + } + + unique_ptr appUserModelId(new wchar_t[appUserModelIdLength]); + returnCode = GetApplicationUserModelId(processHandle.get(), &appUserModelIdLength, appUserModelId.get()); + if (returnCode != ERROR_SUCCESS) + { + return HRESULT_FROM_WIN32(returnCode); + } + + if (CanResolveAppByApplicationUserModelId(appUserModelId.get())) + { + FAST_FAIL(SHStrDup(appUserModelId.get(), applicationUserModelId)); + } + else + { + wchar_t packageFamilyName[PACKAGE_FAMILY_NAME_MAX_LENGTH]; + UINT32 packageFamilyNameLength = ARRAYSIZE(packageFamilyName); + wchar_t packageRelativeAppId[PACKAGE_RELATIVE_APPLICATION_ID_MAX_LENGTH]; + UINT32 packageRelativeAppIdLength = ARRAYSIZE(packageRelativeAppId); + + FAST_FAIL_WIN32(ParseApplicationUserModelId(appUserModelId.get(), &packageFamilyNameLength, packageFamilyName, &packageRelativeAppIdLength, packageRelativeAppId)); + + UINT32 packageCount = 0; + UINT32 packageNamesBufferLength = 0; + FAST_FAIL_BUFFER(FindPackagesByPackageFamily(packageFamilyName, PACKAGE_FILTER_HEAD | PACKAGE_INFORMATION_BASIC, &packageCount, nullptr, &packageNamesBufferLength, nullptr, nullptr)); + + if (packageCount <= 0) + { + return E_NOTFOUND; + } + + unique_ptr packageNames(new PWSTR[packageCount]); + unique_ptr buffer(new wchar_t[packageNamesBufferLength]); + FAST_FAIL_WIN32(FindPackagesByPackageFamily(packageFamilyName, PACKAGE_FILTER_HEAD | PACKAGE_INFORMATION_BASIC, &packageCount, packageNames.get(), &packageNamesBufferLength, buffer.get(), nullptr)); + + PackageInfoReference packageInfoRef; + PACKAGE_INFO_REFERENCE rawPackageInfoRef; + FAST_FAIL_WIN32(OpenPackageInfoByFullName(packageNames[0], 0, &rawPackageInfoRef)); + packageInfoRef.reset(&rawPackageInfoRef); + + UINT32 packageIdsLength = 0; + UINT32 packageIdCount = 0; + FAST_FAIL_BUFFER(GetPackageApplicationIds(*packageInfoRef.get(), &packageIdsLength, nullptr, &packageIdCount)); + + if (packageIdCount <= 0) + { + return E_NOTFOUND; + } + + unique_ptr packageIdsRaw(new BYTE[packageIdsLength]); + FAST_FAIL_WIN32(GetPackageApplicationIds(*packageInfoRef.get(), &packageIdsLength, packageIdsRaw.get(), &packageIdCount)); + + PCWSTR* packageIds = reinterpret_cast(packageIdsRaw.get()); + FAST_FAIL(SHStrDup(packageIds[0], applicationUserModelId)); + } + + return S_OK; +} + +HRESULT AudioSessionService::SetAudioSessionVolume(unsigned long sessionId, float volume) +{ + if (!_sessionMap[sessionId]) + { + return E_INVALIDARG; + } + + CComPtr simpleAudioVolume; + FAST_FAIL(_sessionMap[sessionId]->QueryInterface(IID_PPV_ARGS(&simpleAudioVolume))); + + FAST_FAIL(simpleAudioVolume->SetMasterVolume(volume, nullptr)); + + return S_OK; +} + +HRESULT AudioSessionService::SetAudioSessionMute(unsigned long sessionId, bool isMuted) +{ + if (!_sessionMap[sessionId]) + { + return E_INVALIDARG; + } + + CComPtr simpleAudioVolume; + FAST_FAIL(_sessionMap[sessionId]->QueryInterface(IID_PPV_ARGS(&simpleAudioVolume))); + + FAST_FAIL(simpleAudioVolume->SetMute(isMuted, nullptr)); + return S_OK; +} + +HRESULT AudioSessionService::GetAppProperties(PCWSTR pszAppId, PWSTR* ppszName, PWSTR* ppszIcon, ULONG *background) +{ + *ppszIcon = nullptr; + *ppszName = nullptr; + *background = 0; + + CComPtr item; + FAST_FAIL(SHCreateItemInKnownFolder(FOLDERID_AppsFolder, KF_FLAG_DONT_VERIFY, pszAppId, IID_PPV_ARGS(&item))); + + CComHeapPtr itemName; + FAST_FAIL(item->GetString(PKEY_ItemNameDisplay, &itemName)); + FAST_FAIL(item->GetUInt32(PKEY_AppUserModel_Background, background)); + + CComHeapPtr installPath; + FAST_FAIL(item->GetString(PKEY_AppUserModel_PackageInstallPath, &installPath)); + + CComHeapPtr iconPath; + FAST_FAIL(item->GetString(PKEY_AppUserModel_Icon, &iconPath)); + + CComHeapPtr fullPackagePath; + FAST_FAIL(item->GetString(PKEY_AppUserModel_PackageFullName, &fullPackagePath)); + + CComPtr mrtResMgr; + FAST_FAIL(CoCreateInstance(__uuidof(MrtResourceManager), nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&mrtResMgr))); + FAST_FAIL(mrtResMgr->InitializeForPackage(fullPackagePath)); + CComPtr resourceMap; - FAST_FAIL(mrtResMgr->GetMainResourceMap(IID_PPV_ARGS(&resourceMap))); - + FAST_FAIL(mrtResMgr->GetMainResourceMap(IID_PPV_ARGS(&resourceMap))); + CComHeapPtr resolvedIconPath; - FAST_FAIL(resourceMap->GetFilePath(iconPath, &resolvedIconPath)); - - *ppszIcon = resolvedIconPath.Detach(); - *ppszName = itemName.Detach(); - return S_OK; + FAST_FAIL(resourceMap->GetFilePath(iconPath, &resolvedIconPath)); + + *ppszIcon = resolvedIconPath.Detach(); + *ppszName = itemName.Detach(); + + return S_OK; } \ No newline at end of file diff --git a/EarTrumpet.Interop/AudioSessionService.h b/EarTrumpet.Interop/AudioSessionService.h index 6f358e858..84dd755a1 100644 --- a/EarTrumpet.Interop/AudioSessionService.h +++ b/EarTrumpet.Interop/AudioSessionService.h @@ -14,6 +14,7 @@ namespace EarTrumpet unsigned long BackgroundColor; float Volume; bool IsDesktopApp; + bool IsMuted; }; class AudioSessionService @@ -26,7 +27,7 @@ namespace EarTrumpet HRESULT GetAppProperties(PCWSTR pszAppId, PWSTR* ppszName, PWSTR* ppszIcon, ULONG *background); HRESULT GetAppUserModelIdFromPid(DWORD pid, LPWSTR* applicationUserModelId); HRESULT IsImmersiveProcess(DWORD pid); - HRESULT CanResolveAppByApplicationUserModelId(PCWSTR applicationUserModelId); + HRESULT CanResolveAppByApplicationUserModelId(LPCWSTR applicationUserModelId); std::vector _sessions; std::map> _sessionMap; @@ -45,6 +46,7 @@ namespace EarTrumpet HRESULT GetAudioSessions(void** audioSessions); HRESULT RefreshAudioSessions(); HRESULT SetAudioSessionVolume(unsigned long sessionId, float volume); + HRESULT SetAudioSessionMute(unsigned long sessionId, bool isMuted); }; } } \ No newline at end of file diff --git a/EarTrumpet.Interop/EarTrumpet.Interop.vcxproj b/EarTrumpet.Interop/EarTrumpet.Interop.vcxproj index 5f3d2044b..642d158ee 100644 --- a/EarTrumpet.Interop/EarTrumpet.Interop.vcxproj +++ b/EarTrumpet.Interop/EarTrumpet.Interop.vcxproj @@ -1,115 +1,118 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - - {73FA0BF9-96FE-4C5B-89B2-00B861F3F778} - Win32Proj - EarTrumpetInterop - EarTrumpet.Interop - - - - DynamicLibrary - true - v120 - Unicode - - - DynamicLibrary - false - v120 - true - Unicode - - - - - - - - - - - - - true - $(SolutionDir)Build\$(Configuration)\ - $(ProjectDir)int\$(Configuration)\ - - - false - $(SolutionDir)Build\$(Configuration)\ - $(ProjectDir)int\$(Configuration)\ - false - - - - Level4 - Disabled - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) - - true - ProgramDatabase - StdCall - - - Windows - true - - - pathcch.lib;kernel32.lib;ntdll.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - - - - - - - - - Level4 - MaxSpeed - true - true - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - - false - true - StdCall - - - Windows - true - true - true - - - pathcch.lib;kernel32.lib;ntdll.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - true - - - - - - - - - - - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + + {73FA0BF9-96FE-4C5B-89B2-00B861F3F778} + Win32Proj + EarTrumpetInterop + EarTrumpet.Interop + + + + DynamicLibrary + true + v140 + Unicode + + + DynamicLibrary + false + v140 + true + Unicode + + + + + + + + + + + + + true + $(SolutionDir)Build\$(Configuration)\ + $(ProjectDir)int\$(Configuration)\ + + + false + $(SolutionDir)Build\$(Configuration)\ + $(ProjectDir)int\$(Configuration)\ + false + + + + Level4 + Disabled + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + + true + ProgramDatabase + StdCall + + + Windows + true + + + pathcch.lib;kernel32.lib;ntdll.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + + + Level4 + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + + false + true + StdCall + + + Windows + true + true + true + + + pathcch.lib;kernel32.lib;ntdll.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + true + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/EarTrumpet.Interop/PolicyConfig.h b/EarTrumpet.Interop/PolicyConfig.h new file mode 100644 index 000000000..5135069c5 --- /dev/null +++ b/EarTrumpet.Interop/PolicyConfig.h @@ -0,0 +1,27 @@ +#pragma once + +interface DECLSPEC_UUID("f8679f50-850a-41cf-9c72-430f290290c8") IPolicyConfig; +class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient; + +interface IPolicyConfig : public IUnknown +{ +public: + + virtual HRESULT STDMETHODCALLTYPE Unused1(); + virtual HRESULT STDMETHODCALLTYPE Unused2(); + virtual HRESULT STDMETHODCALLTYPE Unused3(); + virtual HRESULT STDMETHODCALLTYPE Unused4(); + virtual HRESULT STDMETHODCALLTYPE Unused5(); + virtual HRESULT STDMETHODCALLTYPE Unused6(); + virtual HRESULT STDMETHODCALLTYPE Unused7(); + virtual HRESULT STDMETHODCALLTYPE Unused8(); + virtual HRESULT STDMETHODCALLTYPE Unused9(); + virtual HRESULT STDMETHODCALLTYPE Unused10(); + + virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint( + PCWSTR wszDeviceId, + ERole eRole + ); + + // ... +}; \ No newline at end of file diff --git a/EarTrumpet.Interop/exports.cpp b/EarTrumpet.Interop/exports.cpp index 2142a02d3..6cb057e8b 100644 --- a/EarTrumpet.Interop/exports.cpp +++ b/EarTrumpet.Interop/exports.cpp @@ -1,9 +1,13 @@ #include "common.h" #include +#include #include "AudioSessionService.h" +#include "AudioDeviceService.h" using namespace EarTrumpet::Interop; +// Sessions + extern "C" __declspec(dllexport) HRESULT RefreshAudioSessions() { return AudioSessionService::instance()->RefreshAudioSessions(); @@ -22,4 +26,51 @@ extern "C" __declspec(dllexport) HRESULT GetAudioSessions(void** audioSessions) extern "C" __declspec(dllexport) HRESULT SetAudioSessionVolume(unsigned long sessionId, float volume) { return AudioSessionService::instance()->SetAudioSessionVolume(sessionId, volume); -} \ No newline at end of file +} + +extern "C" __declspec(dllexport) HRESULT SetAudioSessionMute(unsigned long sessionId, bool isMuted) +{ + return AudioSessionService::instance()->SetAudioSessionMute(sessionId, isMuted); +} + +// Devices + +extern "C" __declspec(dllexport) HRESULT GetAudioDevices(void** audioDevices) +{ + return AudioDeviceService::instance()->GetAudioDevices(audioDevices); +} + +extern "C" __declspec(dllexport) HRESULT SetDefaultAudioDevice(LPWSTR deviceId) +{ + return AudioDeviceService::instance()->SetDefaultAudioDevice(deviceId); +} + +extern "C" __declspec(dllexport) HRESULT RefreshAudioDevices() +{ + return AudioDeviceService::instance()->RefreshAudioDevices(); +} + +extern "C" __declspec(dllexport) int GetAudioDeviceCount() +{ + return AudioDeviceService::instance()->GetAudioDeviceCount(); +} + +extern "C" __declspec(dllexport) HRESULT GetAudioDeviceVolume(LPWSTR deviceId, float* volume) +{ + return AudioDeviceService::instance()->GetAudioDeviceVolume(deviceId, volume); +} + +extern "C" __declspec(dllexport) HRESULT SetAudioDeviceVolume(LPWSTR deviceId, float volume) +{ + return AudioDeviceService::instance()->SetAudioDeviceVolume(deviceId, volume); +} + +extern "C" __declspec(dllexport) HRESULT MuteAudioDevice(LPWSTR deviceId) +{ + return AudioDeviceService::instance()->MuteAudioDevice(deviceId); +} + +extern "C" __declspec(dllexport) HRESULT UnmuteAudioDevice(LPWSTR deviceId) +{ + return AudioDeviceService::instance()->UnmuteAudioDevice(deviceId); +} diff --git a/EarTrumpet.sln b/EarTrumpet.sln index 0cb072013..8fcdb9aa7 100644 --- a/EarTrumpet.sln +++ b/EarTrumpet.sln @@ -1,31 +1,31 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.31101.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EarTrumpet", "EarTrumpet\EarTrumpet.csproj", "{BA3C7B42-84B0-468C-8640-217E2A24CF81}" - ProjectSection(ProjectDependencies) = postProject - {73FA0BF9-96FE-4C5B-89B2-00B861F3F778} = {73FA0BF9-96FE-4C5B-89B2-00B861F3F778} - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EarTrumpet.Interop", "EarTrumpet.Interop\EarTrumpet.Interop.vcxproj", "{73FA0BF9-96FE-4C5B-89B2-00B861F3F778}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x86 = Debug|x86 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {BA3C7B42-84B0-468C-8640-217E2A24CF81}.Debug|x86.ActiveCfg = Debug|x86 - {BA3C7B42-84B0-468C-8640-217E2A24CF81}.Debug|x86.Build.0 = Debug|x86 - {BA3C7B42-84B0-468C-8640-217E2A24CF81}.Release|x86.ActiveCfg = Release|x86 - {BA3C7B42-84B0-468C-8640-217E2A24CF81}.Release|x86.Build.0 = Release|x86 - {73FA0BF9-96FE-4C5B-89B2-00B861F3F778}.Debug|x86.ActiveCfg = Debug|Win32 - {73FA0BF9-96FE-4C5B-89B2-00B861F3F778}.Debug|x86.Build.0 = Debug|Win32 - {73FA0BF9-96FE-4C5B-89B2-00B861F3F778}.Release|x86.ActiveCfg = Release|Win32 - {73FA0BF9-96FE-4C5B-89B2-00B861F3F778}.Release|x86.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25123.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EarTrumpet", "EarTrumpet\EarTrumpet.csproj", "{BA3C7B42-84B0-468C-8640-217E2A24CF81}" + ProjectSection(ProjectDependencies) = postProject + {73FA0BF9-96FE-4C5B-89B2-00B861F3F778} = {73FA0BF9-96FE-4C5B-89B2-00B861F3F778} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EarTrumpet.Interop", "EarTrumpet.Interop\EarTrumpet.Interop.vcxproj", "{73FA0BF9-96FE-4C5B-89B2-00B861F3F778}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x86 = Debug|x86 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BA3C7B42-84B0-468C-8640-217E2A24CF81}.Debug|x86.ActiveCfg = Debug|x86 + {BA3C7B42-84B0-468C-8640-217E2A24CF81}.Debug|x86.Build.0 = Debug|x86 + {BA3C7B42-84B0-468C-8640-217E2A24CF81}.Release|x86.ActiveCfg = Release|x86 + {BA3C7B42-84B0-468C-8640-217E2A24CF81}.Release|x86.Build.0 = Release|x86 + {73FA0BF9-96FE-4C5B-89B2-00B861F3F778}.Debug|x86.ActiveCfg = Debug|Win32 + {73FA0BF9-96FE-4C5B-89B2-00B861F3F778}.Debug|x86.Build.0 = Debug|Win32 + {73FA0BF9-96FE-4C5B-89B2-00B861F3F778}.Release|x86.ActiveCfg = Release|Win32 + {73FA0BF9-96FE-4C5B-89B2-00B861F3F778}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/EarTrumpet/EarTrumpet.csproj b/EarTrumpet/EarTrumpet.csproj index ff97fde56..c615e532c 100644 --- a/EarTrumpet/EarTrumpet.csproj +++ b/EarTrumpet/EarTrumpet.csproj @@ -1,127 +1,160 @@ - - - - - Debug - AnyCPU - {BA3C7B42-84B0-468C-8640-217E2A24CF81} - WinExe - Properties - EarTrumpet - EarTrumpet - v4.5 - 512 - {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 4 - - - Application.ico - - - true - ..\Build\Debug\ - DEBUG;TRACE - full - x86 - prompt - MinimumRecommendedRules.ruleset - true - 4 - - - true - - - ..\Build\Release\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - true - - - - - - - - 4.0 - - - - - - - - MSBuild:Compile - Designer - - - - - - - True - True - Resources.resx - - - - - - - - - - - - - - - - - - - MSBuild:Compile - Designer - - - App.xaml - Code - - - MainWindow.xaml - Code - - - - - Code - - - - - - - - - - - - - - PublicResXFileCodeGenerator - Resources.Designer.cs - - - + + + + + Debug + AnyCPU + {BA3C7B42-84B0-468C-8640-217E2A24CF81} + WinExe + Properties + EarTrumpet + EarTrumpet + v4.5 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + + + Application.ico + + + true + ..\Build\Debug\ + DEBUG;TRACE + full + x86 + prompt + MinimumRecommendedRules.ruleset + true + 4 + + + true + + + ..\Build\Release\ + TRACE + true + pdbonly + x86 + prompt + MinimumRecommendedRules.ruleset + true + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + + + + + + + + Resources.de.resx + True + True + + + Resources.nn-no.resx + True + True + + + Resources.nb-no.resx + True + True + + + True + True + Resources.resx + + + + + + + + + + + + + + + + + + + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + MainWindow.xaml + Code + + + + + Code + + + + + + + + + + + + + + PublicResXFileCodeGenerator + Resources.de.Designer.cs + + + PublicResXFileCodeGenerator + Resources.nn-no.Designer.cs + + + PublicResXFileCodeGenerator + Resources.nb-no.Designer.cs + + + PublicResXFileCodeGenerator + Resources.Designer.cs + + + + + --> \ No newline at end of file diff --git a/EarTrumpet/EarTrumpet.iss b/EarTrumpet/EarTrumpet.iss index 3e63bf729..a0fd8c3c6 100644 --- a/EarTrumpet/EarTrumpet.iss +++ b/EarTrumpet/EarTrumpet.iss @@ -23,18 +23,23 @@ DisableStartupPrompt=yes DisableWelcomePage=yes MinVersion=10.0 SetupMutex=EarTrumpetSetup +AppMutex="Local\{{02639d71-0935-35e8-9d1b-9dd1a2a34627}{{EarTrumpet}" [Files] Source: "EarTrumpet.exe"; DestDir: "{app}"; Flags: replacesameversion Source: "EarTrumpet.exe.config"; DestDir: "{app}"; Flags: replacesameversion Source: "EarTrumpet.Interop.dll"; DestDir: "{app}"; Flags: replacesameversion -Source: "..\Redists\vcredist_x86.exe"; DestDir: "{tmp}"; +Source: "de\*.*"; DestDir: "{app}\de"; Flags: replacesameversion recursesubdirs +Source: "fr\*.*"; DestDir: "{app}\fr"; Flags: replacesameversion recursesubdirs +Source: "nb-no\*.*"; DestDir: "{app}\nb-no"; Flags: replacesameversion recursesubdirs +Source: "nn-no\*.*"; DestDir: "{app}\nn-no"; Flags: replacesameversion recursesubdirs +Source: "..\Redists\vc_redist.x86.exe"; DestDir: "{tmp}"; [Icons] Name: "{commonstartmenu}\Ear Trumpet"; Filename: "{app}\EarTrumpet.exe" Name: "{commonstartup}\Ear Trumpet"; Filename: "{app}\EarTrumpet.exe" [Run] -Filename: "{tmp}\vcredist_x86.exe"; Parameters: "/install /passive /norestart"; StatusMsg: Installing VC++ 2013 runtime... +Filename: "{tmp}\vc_redist.x86.exe"; Parameters: "/install /passive /norestart"; StatusMsg: Installing VC++ 2015 runtime... Filename: "{app}\EarTrumpet.exe"; Description: "Run Ear Trumpet"; Flags: "postinstall nowait runasoriginaluser" diff --git a/EarTrumpet/Extensions/BlurWindowExtensions.cs b/EarTrumpet/Extensions/BlurWindowExtensions.cs index 130d960a4..3b2da4bad 100644 --- a/EarTrumpet/Extensions/BlurWindowExtensions.cs +++ b/EarTrumpet/Extensions/BlurWindowExtensions.cs @@ -101,7 +101,7 @@ private static Interop.AccentFlags GetAccentFlagsForTaskbarPosition() { var flags = Interop.AccentFlags.DrawAllBorders; - switch(TaskbarService.TaskbarPosition) + switch(TaskbarService.GetWinTaskbarState().TaskbarPosition) { case TaskbarPosition.Top: flags &= ~Interop.AccentFlags.DrawTopBorder; diff --git a/EarTrumpet/Extensions/IconExtensions.cs b/EarTrumpet/Extensions/IconExtensions.cs index cb17f310a..cd99cb1fb 100644 --- a/EarTrumpet/Extensions/IconExtensions.cs +++ b/EarTrumpet/Extensions/IconExtensions.cs @@ -35,9 +35,9 @@ public static ImageSource ToImageSource(this Icon icon) return bitmapSource; } - public static ImageSource ExtractIconFromDll(this string path) + public static ImageSource ExtractIconFromDll(this string path, int iconIndex = 0) { - var iconPtr = ExtractIcon(Process.GetCurrentProcess().Handle, path, 0); + var iconPtr = ExtractIcon(Process.GetCurrentProcess().Handle, path, iconIndex); var image = Imaging.CreateBitmapSourceFromHIcon(iconPtr, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); DestroyIcon(iconPtr); return image; diff --git a/EarTrumpet/Extensions/InvertableVisibilityConverter.cs b/EarTrumpet/Extensions/InvertableVisibilityConverter.cs new file mode 100644 index 000000000..1c358355f --- /dev/null +++ b/EarTrumpet/Extensions/InvertableVisibilityConverter.cs @@ -0,0 +1,32 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace EarTrumpet.Extensions +{ + [ValueConversion(typeof(bool), typeof(Visibility))] + public class InvertableVisibilityConverter : IValueConverter + { + enum Parameters + { + Normal, Inverted + } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var boolValue = (bool)value; + var direction = (Parameters)Enum.Parse(typeof(Parameters), (string)parameter); + + if (direction == Parameters.Inverted) + return !boolValue ? Visibility.Visible : Visibility.Hidden; + + return boolValue ? Visibility.Visible : Visibility.Hidden; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return null; + } + } +} diff --git a/EarTrumpet/Extensions/OpacityConverter.cs b/EarTrumpet/Extensions/OpacityConverter.cs new file mode 100644 index 000000000..88d00d98d --- /dev/null +++ b/EarTrumpet/Extensions/OpacityConverter.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Data; + +namespace EarTrumpet.Extensions +{ + public class OpacityConverter: IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return (bool)value ? 0.4 : 1.0; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/EarTrumpet/Extensions/WindowExtensions.cs b/EarTrumpet/Extensions/WindowExtensions.cs index a4fdae40f..65c9d9415 100644 --- a/EarTrumpet/Extensions/WindowExtensions.cs +++ b/EarTrumpet/Extensions/WindowExtensions.cs @@ -8,90 +8,106 @@ namespace EarTrumpet.Extensions { internal static class WindowExtensions { - private static bool _windowVisible; - - public static bool IsWindowVisible(this Window window) - { - return _windowVisible; - } - + private static bool hideAnimationInProgress = false; public static void HideWithAnimation(this Window window) { - var hideAnimation = new DoubleAnimation - { - Duration = new Duration(TimeSpan.FromSeconds(0.2)), - FillBehavior = FillBehavior.Stop, - EasingFunction = new ExponentialEase {EasingMode = EasingMode.EaseIn} - }; - var taskbarPosition = TaskbarService.TaskbarPosition; - switch (taskbarPosition) - { - case TaskbarPosition.Left: - case TaskbarPosition.Right: - hideAnimation.From = window.Left; - break; - default: - hideAnimation.From = window.Top; - break; - } - hideAnimation.To = (taskbarPosition == TaskbarPosition.Top || taskbarPosition == TaskbarPosition.Left) ? hideAnimation.From - 10 : hideAnimation.From + 10; - hideAnimation.Completed += (s, e) => + if (hideAnimationInProgress) return; + + try { - window.Visibility = Visibility.Hidden; - }; + hideAnimationInProgress = true; + + var hideAnimation = new DoubleAnimation + { + Duration = new Duration(TimeSpan.FromSeconds(0.2)), + FillBehavior = FillBehavior.Stop, + EasingFunction = new ExponentialEase { EasingMode = EasingMode.EaseIn } + }; + var taskbarPosition = TaskbarService.GetWinTaskbarState().TaskbarPosition; + switch (taskbarPosition) + { + case TaskbarPosition.Left: + case TaskbarPosition.Right: + hideAnimation.From = window.Left; + break; + default: + hideAnimation.From = window.Top; + break; + } + hideAnimation.To = (taskbarPosition == TaskbarPosition.Top || taskbarPosition == TaskbarPosition.Left) ? hideAnimation.From - 10 : hideAnimation.From + 10; + hideAnimation.Completed += (s, e) => + { + window.Visibility = Visibility.Hidden; + hideAnimationInProgress = false; + }; - switch (taskbarPosition) + switch (taskbarPosition) + { + case TaskbarPosition.Left: + case TaskbarPosition.Right: + window.ApplyAnimationClock(Window.LeftProperty, hideAnimation.CreateClock()); + break; + default: + window.ApplyAnimationClock(Window.TopProperty, hideAnimation.CreateClock()); + break; + } + } + catch { - case TaskbarPosition.Left: - case TaskbarPosition.Right: - window.ApplyAnimationClock(Window.LeftProperty, hideAnimation.CreateClock()); - break; - default: - window.ApplyAnimationClock(Window.TopProperty, hideAnimation.CreateClock()); - break; + hideAnimationInProgress = false; } - _windowVisible = false; } + private static bool showAnimationInProgress = false; public static void ShowwithAnimation(this Window window) - { - window.Visibility = Visibility.Visible; - window.Topmost = false; - window.Activate(); - var showAnimation = new DoubleAnimation - { - Duration = new Duration(TimeSpan.FromSeconds(0.3)), - FillBehavior = FillBehavior.Stop, - EasingFunction = new ExponentialEase { EasingMode = EasingMode.EaseOut } - }; - var taskbarPosition = TaskbarService.TaskbarPosition; - switch (taskbarPosition) + { + if (showAnimationInProgress) return; + + try { - case TaskbarPosition.Left: - case TaskbarPosition.Right: - showAnimation.To = window.Left; - break; - default: - showAnimation.To = window.Top; - break; + showAnimationInProgress = true; + window.Visibility = Visibility.Visible; + window.Topmost = false; + window.Activate(); + var showAnimation = new DoubleAnimation + { + Duration = new Duration(TimeSpan.FromSeconds(0.3)), + FillBehavior = FillBehavior.Stop, + EasingFunction = new ExponentialEase { EasingMode = EasingMode.EaseOut } + }; + var taskbarPosition = TaskbarService.GetWinTaskbarState().TaskbarPosition; + switch (taskbarPosition) + { + case TaskbarPosition.Left: + case TaskbarPosition.Right: + showAnimation.To = window.Left; + break; + default: + showAnimation.To = window.Top; + break; + } + showAnimation.From = (taskbarPosition == TaskbarPosition.Top || taskbarPosition == TaskbarPosition.Left) ? showAnimation.To - 25 : showAnimation.To + 25; + showAnimation.Completed += (s, e) => + { + window.Topmost = true; + showAnimationInProgress = false; + window.Focus(); + }; + switch (taskbarPosition) + { + case TaskbarPosition.Left: + case TaskbarPosition.Right: + window.ApplyAnimationClock(Window.LeftProperty, showAnimation.CreateClock()); + break; + default: + window.ApplyAnimationClock(Window.TopProperty, showAnimation.CreateClock()); + break; + } } - showAnimation.From = (taskbarPosition == TaskbarPosition.Top || taskbarPosition == TaskbarPosition.Left) ? showAnimation.To - 25 : showAnimation.To + 25; - showAnimation.Completed += (s, e) => - { - window.Topmost = true; - window.Focus(); - }; - switch (taskbarPosition) + catch { - case TaskbarPosition.Left: - case TaskbarPosition.Right: - window.ApplyAnimationClock(Window.LeftProperty, showAnimation.CreateClock()); - break; - default: - window.ApplyAnimationClock(Window.TopProperty, showAnimation.CreateClock()); - break; + showAnimationInProgress = false; } - _windowVisible = true; } public static Matrix CalculateDpiFactors(this Window window) diff --git a/EarTrumpet/MainWindow.xaml b/EarTrumpet/MainWindow.xaml index 949beb8ec..82dae45fa 100644 --- a/EarTrumpet/MainWindow.xaml +++ b/EarTrumpet/MainWindow.xaml @@ -3,6 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:p="clr-namespace:EarTrumpet.Properties" xmlns:viewModels="clr-namespace:EarTrumpet.ViewModels" + xmlns:ext="clr-namespace:EarTrumpet.Extensions" Title="Ear Trumpet" MaxHeight="600" Width="360" @@ -10,6 +11,8 @@ Topmost="True" ShowInTaskbar="False" AllowsTransparency="True" + SnapsToDevicePixels="True" + UseLayoutRounding="True" WindowStyle="None" Deactivated="Window_Deactivated" PreviewKeyDown="Window_PreviewKeyDown" @@ -23,19 +26,19 @@ - - + Color="#39FFFFFF" /> + + +