Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mpc-hc/sanear.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/dll
diff options
context:
space:
mode:
authorAlex Marsev <alex.marsev@gmail.com>2015-07-20 21:11:02 +0300
committerAlex Marsev <alex.marsev@gmail.com>2015-07-20 21:12:46 +0300
commit5b55a3f90043fa3248b937b073a3242d24058120 (patch)
tree7971198a224886e22605433efceb58bcdddbf164 /dll
parentd2621161af7b266ca639de8c9c0adb0ef3052f88 (diff)
Add tray icon with context menu to dll version
Code still needs some refactoring. Icon bitmap is an ugly stock.
Diffstat (limited to 'dll')
-rw-r--r--dll/src/sanear-dll.vcxproj2
-rw-r--r--dll/src/sanear-dll/OuterFilter.cpp3
-rw-r--r--dll/src/sanear-dll/OuterFilter.h4
-rw-r--r--dll/src/sanear-dll/TrayWindow.cpp388
-rw-r--r--dll/src/sanear-dll/TrayWindow.h44
-rw-r--r--dll/src/sanear-dll/pch.h35
6 files changed, 475 insertions, 1 deletions
diff --git a/dll/src/sanear-dll.vcxproj b/dll/src/sanear-dll.vcxproj
index 425941e..a1c7a57 100644
--- a/dll/src/sanear-dll.vcxproj
+++ b/dll/src/sanear-dll.vcxproj
@@ -133,11 +133,13 @@
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="sanear-dll\RegistryKey.cpp" />
+ <ClCompile Include="sanear-dll\TrayWindow.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="sanear-dll\OuterFilter.h" />
<ClInclude Include="sanear-dll\pch.h" />
<ClInclude Include="sanear-dll\RegistryKey.h" />
+ <ClInclude Include="sanear-dll\TrayWindow.h" />
</ItemGroup>
<ItemGroup>
<None Include="sanear-dll\sanear.def" />
diff --git a/dll/src/sanear-dll/OuterFilter.cpp b/dll/src/sanear-dll/OuterFilter.cpp
index 98be314..084e869 100644
--- a/dll/src/sanear-dll/OuterFilter.cpp
+++ b/dll/src/sanear-dll/OuterFilter.cpp
@@ -1,6 +1,8 @@
#include "pch.h"
#include "OuterFilter.h"
+#include "../../../src/Factory.h"
+
namespace SaneAudioRenderer
{
namespace
@@ -64,6 +66,7 @@ namespace SaneAudioRenderer
ReturnIfFailed(Factory::CreateSettings(&m_settings))
ReturnIfFailed(Factory::CreateFilterAggregated(GetOwner(), m_guid, m_settings, &m_innerFilter));
ReturnIfFailed(m_registryKey.Open(HKEY_CURRENT_USER, L"Software\\sanear"));
+ ReturnIfFailed(m_trayWindow.Init(m_settings));
m_initialized = true;
diff --git a/dll/src/sanear-dll/OuterFilter.h b/dll/src/sanear-dll/OuterFilter.h
index f44b85f..cf4de20 100644
--- a/dll/src/sanear-dll/OuterFilter.h
+++ b/dll/src/sanear-dll/OuterFilter.h
@@ -1,8 +1,9 @@
#pragma once
#include "RegistryKey.h"
+#include "TrayWindow.h"
-#include "../../../src/Factory.h"
+#include "../../../src/Interfaces.h"
namespace SaneAudioRenderer
{
@@ -29,5 +30,6 @@ namespace SaneAudioRenderer
RegistryKey m_registryKey;
ISettingsPtr m_settings;
IUnknownPtr m_innerFilter;
+ TrayWindow m_trayWindow;
};
}
diff --git a/dll/src/sanear-dll/TrayWindow.cpp b/dll/src/sanear-dll/TrayWindow.cpp
new file mode 100644
index 0000000..a3fc120
--- /dev/null
+++ b/dll/src/sanear-dll/TrayWindow.cpp
@@ -0,0 +1,388 @@
+#include "pch.h"
+#include "TrayWindow.h"
+
+namespace SaneAudioRenderer
+{
+ namespace
+ {
+ const auto WindowClass = L"SaneAudioRenderer::TrayWindow";
+ const auto WindowTitle = L"";
+
+ enum
+ {
+ WM_TRAYNOTIFY = WM_USER + 100,
+ };
+
+ enum Item
+ {
+ ExclusiveMode = 10,
+ AllowBitstreaming,
+ EnableCrossfeed,
+ CrossfeedCMoy, // used in CheckMenuRadioItem()
+ CrossfeedJMeier, // used in CheckMenuRadioItem()
+ DefaultDevice, // needs to be last
+ };
+
+ std::vector<std::pair<std::wstring, std::wstring>> GetDevices()
+ {
+ std::vector<std::pair<std::wstring, std::wstring>> devices;
+
+ IMMDeviceEnumeratorPtr enumerator;
+ IMMDeviceCollectionPtr collection;
+ UINT count = 0;
+
+ if (SUCCEEDED(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&enumerator))) &&
+ SUCCEEDED(enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE | DEVICE_STATE_UNPLUGGED, &collection)) &&
+ SUCCEEDED(collection->GetCount(&count)))
+ {
+ for (UINT i = 0; i < count; i++)
+ {
+ IMMDevicePtr device;
+ LPWSTR pDeviceId;
+ IPropertyStorePtr devicePropertyStore;
+ PROPVARIANT friendlyName; // TODO: make wrapper class this, use it also in AudioDeviceManager
+ PropVariantInit(&friendlyName);
+
+ if (SUCCEEDED(collection->Item(i, &device)) &&
+ SUCCEEDED(device->GetId(&pDeviceId)) &&
+ SUCCEEDED(device->OpenPropertyStore(STGM_READ, &devicePropertyStore)) &&
+ SUCCEEDED(devicePropertyStore->GetValue(PKEY_Device_FriendlyName, &friendlyName)))
+ {
+ std::unique_ptr<WCHAR, CoTaskMemFreeDeleter> holder(pDeviceId);
+ devices.emplace_back(friendlyName.pwszVal, pDeviceId);
+ PropVariantClear(&friendlyName);
+ }
+ }
+ }
+
+ return devices;
+ }
+ }
+
+ TrayWindow::TrayWindow()
+ {
+ m_nid = {sizeof(m_nid)};
+ }
+
+ TrayWindow::~TrayWindow()
+ {
+ Destroy();
+ }
+
+ HRESULT TrayWindow::Init(ISettings* pSettings)
+ {
+ CheckPointer(pSettings, E_POINTER);
+
+ assert(m_hThread == NULL);
+ Destroy();
+
+ ReturnIfFailed(pSettings->QueryInterface(IID_PPV_ARGS(&m_settings)));
+
+ m_hThread = (HANDLE)_beginthreadex(nullptr, 0, StaticThreadProc<TrayWindow>, this, 0, nullptr);
+
+ if (m_hThread == NULL || !m_windowCreated.get_future().get())
+ return E_FAIL;
+
+ return S_OK;
+ }
+
+ void TrayWindow::Destroy()
+ {
+ if (m_hThread != NULL)
+ {
+ PostMessage(m_hWindow, WM_DESTROY, 0, 0);
+ WaitForSingleObject(m_hThread, INFINITE);
+ CloseHandle(m_hThread);
+ m_hThread = NULL;
+ }
+ }
+
+ void TrayWindow::AddIcon()
+ {
+ m_nid.hWnd = m_hWindow;
+ m_nid.uVersion = NOTIFYICON_VERSION_4;
+ m_nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_SHOWTIP;
+ m_nid.hIcon = LoadIcon(NULL, IDI_APPLICATION);
+ m_nid.uCallbackMessage = WM_TRAYNOTIFY;
+ lstrcpy(m_nid.szTip, L"sanear");
+
+ Shell_NotifyIcon(NIM_ADD, &m_nid);
+ Shell_NotifyIcon(NIM_SETVERSION, &m_nid);
+ }
+
+ void TrayWindow::RemoveIcon()
+ {
+ Shell_NotifyIcon(NIM_DELETE, &m_nid);
+ }
+
+ void TrayWindow::AddMenu()
+ {
+ RemoveMenu();
+
+ m_hMenu = CreateMenu();
+ HMENU hMenu = CreateMenu();
+
+ BOOL allowBitstreaming;
+ m_settings->GetAllowBitstreaming(&allowBitstreaming);
+
+ BOOL crossfeedEnabled;
+ m_settings->GetCrossfeedEnabled(&crossfeedEnabled);
+
+ UINT32 crosfeedCutoff;
+ UINT32 crosfeedLevel;
+ m_settings->GetCrossfeedSettings(&crosfeedCutoff, &crosfeedLevel);
+
+ LPWSTR pDeviceId = nullptr;
+ BOOL exclusive;
+ m_settings->GetOuputDevice(&pDeviceId, &exclusive, nullptr);
+ std::unique_ptr<WCHAR, CoTaskMemFreeDeleter> holder(pDeviceId); // TODO: write specialized wrapper for this
+
+ try
+ {
+ m_devices = GetDevices();
+ }
+ catch (std::bad_alloc&)
+ {
+ m_devices.clear();
+ }
+
+ MENUITEMINFO separator = {sizeof(MENUITEMINFO)};
+ separator.fMask = MIIM_TYPE;
+ separator.fType = MFT_SEPARATOR;
+
+ MENUITEMINFO check = {sizeof(MENUITEMINFO)};
+ check.fMask = MIIM_STRING | MIIM_ID | MIIM_CHECKMARKS | MIIM_STATE;
+
+ MENUITEMINFO submenu = {sizeof(MENUITEMINFO)};
+ submenu.fMask = MIIM_SUBMENU;
+
+ check.wID = Item::AllowBitstreaming;
+ check.dwTypeData = L"Allow bitstreaming (in exclusive WASAPI mode)";
+ check.fState = (allowBitstreaming ? MFS_CHECKED : MFS_UNCHECKED) | (exclusive ? MFS_ENABLED : MFS_DISABLED);
+ InsertMenuItem(hMenu, 0, TRUE, &check);
+
+ check.wID = Item::ExclusiveMode;
+ check.dwTypeData = L"Exclusive WASAPI mode";
+ check.fState = (exclusive ? MFS_CHECKED : MFS_UNCHECKED);
+ InsertMenuItem(hMenu, 0, TRUE, &check);
+
+ InsertMenuItem(hMenu, 0, TRUE, &separator);
+
+ check.wID = Item::CrossfeedJMeier;
+ check.dwTypeData = L"J.Meier-like preset";
+ check.fState = (crossfeedEnabled ? MFS_ENABLED : MFS_DISABLED);
+ InsertMenuItem(hMenu, 0, TRUE, &check);
+
+ check.wID = Item::CrossfeedCMoy;
+ check.dwTypeData = L"C.Moy-like preset";
+ check.fState = (crossfeedEnabled ? MFS_ENABLED : MFS_DISABLED);
+ InsertMenuItem(hMenu, 0, TRUE, &check);
+
+ if (crosfeedCutoff == ISettings::CROSSFEED_CUTOFF_FREQ_CMOY &&
+ crosfeedLevel == ISettings::CROSSFEED_LEVEL_CMOY)
+ {
+ CheckMenuRadioItem(hMenu, Item::CrossfeedCMoy, Item::CrossfeedJMeier, Item::CrossfeedCMoy, MF_BYCOMMAND);
+ }
+ else if (crosfeedCutoff == ISettings::CROSSFEED_CUTOFF_FREQ_JMEIER &&
+ crosfeedLevel == ISettings::CROSSFEED_LEVEL_JMEIER)
+ {
+ CheckMenuRadioItem(hMenu, Item::CrossfeedCMoy, Item::CrossfeedJMeier, Item::CrossfeedJMeier, MF_BYCOMMAND);
+ }
+
+ check.wID = Item::EnableCrossfeed;
+ check.dwTypeData = L"Enable stereo crossfeed";
+ check.fState = (crossfeedEnabled ? MFS_CHECKED : MFS_UNCHECKED);
+ InsertMenuItem(hMenu, 0, TRUE, &check);
+
+ InsertMenuItem(hMenu, 0, TRUE, &separator);
+
+ UINT selectedDevice = Item::DefaultDevice;
+
+ for (size_t i = 0, n = m_devices.size(); i < n; i++)
+ {
+ const auto& device = m_devices[n - i - 1];
+
+ check.wID = Item::DefaultDevice + (UINT)(n - i);
+ check.dwTypeData = (LPWSTR)device.first.c_str();
+ check.fState = MFS_ENABLED;
+ InsertMenuItem(hMenu, 0, TRUE, &check);
+
+ if (pDeviceId && device.second == pDeviceId)
+ selectedDevice = check.wID;
+ }
+
+ check.wID = Item::DefaultDevice;
+ check.dwTypeData = L"Default Device";
+ check.fState = MFS_ENABLED;
+ InsertMenuItem(hMenu, 0, TRUE, &check);
+
+ CheckMenuRadioItem(hMenu, Item::DefaultDevice, Item::DefaultDevice + (UINT)m_devices.size(), selectedDevice, MF_BYCOMMAND);
+
+ submenu.hSubMenu = hMenu;
+ InsertMenuItem(m_hMenu, 0, TRUE, &submenu);
+ }
+
+ void TrayWindow::RemoveMenu()
+ {
+ if (m_hMenu)
+ {
+ EndMenu();
+ DestroyMenu(m_hMenu);
+ m_hMenu = NULL;
+ }
+ }
+
+ void TrayWindow::OnTrayNotify(WPARAM wParam, LPARAM lParam)
+ {
+ switch (LOWORD(lParam))
+ {
+ case NIN_KEYSELECT:
+ case NIN_SELECT:
+ case WM_CONTEXTMENU:
+ AddMenu();
+ SetForegroundWindow(m_hWindow);
+ TrackPopupMenuEx(GetSubMenu(m_hMenu, 0), TPM_LEFTALIGN | TPM_BOTTOMALIGN, LOWORD(wParam), HIWORD(wParam), m_hWindow, NULL);
+ break;
+ }
+ }
+
+ void TrayWindow::OnCommand(WPARAM wParam, LPARAM lParam)
+ {
+ switch (wParam)
+ {
+ case Item::ExclusiveMode:
+ {
+ LPWSTR pDeviceId = nullptr;
+ BOOL exclusive;
+ UINT32 buffer;
+ m_settings->GetOuputDevice(&pDeviceId, &exclusive, &buffer);
+ std::unique_ptr<WCHAR, CoTaskMemFreeDeleter> holder(pDeviceId);
+ m_settings->SetOuputDevice(pDeviceId, !exclusive, buffer);
+ break;
+ }
+
+ case Item::AllowBitstreaming:
+ {
+ BOOL value;
+ m_settings->GetAllowBitstreaming(&value);
+ m_settings->SetAllowBitstreaming(!value);
+ break;
+ }
+
+ case Item::EnableCrossfeed:
+ {
+ BOOL value;
+ m_settings->GetCrossfeedEnabled(&value);
+ m_settings->SetCrossfeedEnabled(!value);
+ break;
+ }
+
+ case Item::CrossfeedCMoy:
+ {
+ m_settings->SetCrossfeedSettings(ISettings::CROSSFEED_CUTOFF_FREQ_CMOY, ISettings::CROSSFEED_LEVEL_CMOY);
+ break;
+ }
+
+ case Item::CrossfeedJMeier:
+ {
+ m_settings->SetCrossfeedSettings(ISettings::CROSSFEED_CUTOFF_FREQ_JMEIER, ISettings::CROSSFEED_LEVEL_JMEIER);
+ break;
+ }
+
+ case Item::DefaultDevice:
+ {
+ LPWSTR pDeviceId = nullptr;
+ BOOL exclusive;
+ UINT32 buffer;
+ m_settings->GetOuputDevice(&pDeviceId, &exclusive, &buffer);
+ std::unique_ptr<WCHAR, CoTaskMemFreeDeleter> holder(pDeviceId);
+
+ if (pDeviceId && *pDeviceId)
+ m_settings->SetOuputDevice(nullptr, exclusive, buffer);
+
+ break;
+ }
+
+ default:
+ {
+ if (wParam <= Item::DefaultDevice || wParam > Item::DefaultDevice + m_devices.size())
+ break;
+
+ const auto& selection = m_devices[wParam - Item::DefaultDevice - 1].second;
+
+ LPWSTR pDeviceId = nullptr;
+ BOOL exclusive;
+ UINT32 buffer;
+ m_settings->GetOuputDevice(&pDeviceId, &exclusive, &buffer);
+ std::unique_ptr<WCHAR, CoTaskMemFreeDeleter> holder(pDeviceId);
+
+ if (!pDeviceId || selection != pDeviceId)
+ m_settings->SetOuputDevice(selection.c_str(), exclusive, buffer);
+ }
+ }
+ }
+
+ DWORD TrayWindow::ThreadProc()
+ {
+ CoInitializeHelper coInitializeHelper(COINIT_MULTITHREADED);
+
+ if (!coInitializeHelper.Initialized())
+ {
+ m_windowCreated.set_value(false);
+ return 0;
+ }
+
+ WNDCLASSEX windowClass = {
+ sizeof(windowClass), 0, StaticWindowProc<TrayWindow>, 0, 0, g_hInst,
+ NULL, NULL, NULL, nullptr, WindowClass, NULL
+ };
+
+ RegisterClassEx(&windowClass);
+
+ m_hWindow = CreateWindowEx(0, WindowClass, WindowTitle, 0, 0, 0, 0, 0, 0, NULL, g_hInst, this);
+
+ if (m_hWindow == NULL)
+ {
+ m_windowCreated.set_value(false);
+ return 0;
+ }
+
+ m_windowCreated.set_value(true);
+
+ m_taskbarCreatedMessage = RegisterWindowMessage(L"TaskbarCreated");
+
+ AddIcon();
+
+ RunMessageLoop();
+
+ return 0;
+ }
+
+ LRESULT TrayWindow::WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
+ {
+ if (msg == m_taskbarCreatedMessage)
+ {
+ AddIcon();
+ return 0;
+ }
+
+ switch (msg)
+ {
+ case WM_COMMAND:
+ OnCommand(wParam, lParam);
+ return 0;
+
+ case WM_TRAYNOTIFY:
+ OnTrayNotify(wParam, lParam);
+ return 0;
+
+ case WM_DESTROY:
+ RemoveMenu();
+ RemoveIcon();
+ PostQuitMessage(0);
+ return 0;
+ }
+
+ return DefWindowProc(hWnd, msg, wParam, lParam);
+ }
+}
diff --git a/dll/src/sanear-dll/TrayWindow.h b/dll/src/sanear-dll/TrayWindow.h
new file mode 100644
index 0000000..6e390d1
--- /dev/null
+++ b/dll/src/sanear-dll/TrayWindow.h
@@ -0,0 +1,44 @@
+#pragma once
+
+#include "../../../src/Interfaces.h"
+
+namespace SaneAudioRenderer
+{
+ class TrayWindow final
+ {
+ public:
+
+ TrayWindow();
+ ~TrayWindow();
+ TrayWindow(const TrayWindow&) = delete;
+ TrayWindow& operator=(const TrayWindow&) = delete;
+
+ HRESULT Init(ISettings* pSettings);
+
+ private:
+
+ void Destroy();
+
+ void AddIcon();
+ void RemoveIcon();
+
+ void AddMenu();
+ void RemoveMenu();
+
+ void OnTrayNotify(WPARAM wParam, LPARAM lParam);
+ void OnCommand(WPARAM wParam, LPARAM lParam);
+
+ DWORD ThreadProc();
+ LRESULT WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
+
+ UINT m_taskbarCreatedMessage = 0;
+ NOTIFYICONDATA m_nid;
+
+ ISettingsPtr m_settings;
+ HANDLE m_hThread = NULL;
+ HWND m_hWindow = NULL;
+ HMENU m_hMenu = NULL;
+ std::promise<bool> m_windowCreated;
+ std::vector<std::pair<std::wstring, std::wstring>> m_devices;
+ };
+}
diff --git a/dll/src/sanear-dll/pch.h b/dll/src/sanear-dll/pch.h
index 434371f..756549e 100644
--- a/dll/src/sanear-dll/pch.h
+++ b/dll/src/sanear-dll/pch.h
@@ -2,4 +2,39 @@
#include "../../../src/pch.h"
+namespace SaneAudioRenderer
+{
+ template <class T, DWORD(T::*ThreadProc)() = &T::ThreadProc>
+ unsigned CALLBACK StaticThreadProc(LPVOID p)
+ {
+ return (static_cast<T*>(p)->*ThreadProc)();
+ }
+
+ template <class T, LRESULT(T::*WindowProc)(HWND, UINT, WPARAM, LPARAM) = &T::WindowProc>
+ LRESULT CALLBACK StaticWindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
+ {
+ if (LONG_PTR userData = GetWindowLongPtr(hWnd, GWLP_USERDATA))
+ return (reinterpret_cast<T*>(userData)->*WindowProc)(hWnd, msg, wParam, lParam);
+
+ if (msg == WM_NCCREATE)
+ {
+ CREATESTRUCT* pCreateStruct = reinterpret_cast<CREATESTRUCT*>(lParam);
+ SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pCreateStruct->lpCreateParams));
+ return (static_cast<T*>(pCreateStruct->lpCreateParams)->*WindowProc)(hWnd, msg, wParam, lParam);
+ }
+
+ return DefWindowProc(hWnd, msg, wParam, lParam);
+ }
+
+ inline void RunMessageLoop()
+ {
+ MSG msg;
+ while (GetMessage(&msg, NULL, 0, 0))
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ }
+}
+
_COM_SMARTPTR_TYPEDEF(IFilterMapper2, __uuidof(IFilterMapper2));