diff --git a/AudioPlaybackConnector.cpp b/AudioPlaybackConnector.cpp index 78a23fc..2a341a3 100644 --- a/AudioPlaybackConnector.cpp +++ b/AudioPlaybackConnector.cpp @@ -8,6 +8,7 @@ winrt::fire_and_forget ConnectDevice(DevicePicker, std::wstring_view); void SetupDevicePicker(); void SetupSvgIcon(); void UpdateNotifyIcon(); +void ShowInitialToastNotification(); int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, @@ -20,6 +21,31 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, g_hInst = hInstance; + g_hMutex = CreateMutexW(NULL, TRUE, UNIQUE_MUTEX_NAME); + if (g_hMutex == NULL) + { + TaskDialog(nullptr, nullptr, _(L"Error"), nullptr, _(L"Could not create mutex. Application will exit."), TDCBF_OK_BUTTON, TD_ERROR_ICON, nullptr); + return EXIT_FAILURE; + } + + if (GetLastError() == ERROR_ALREADY_EXISTS) + { + CloseHandle(g_hMutex); + g_hMutex = NULL; + + HWND hExistingWnd = FindWindowW(L"AudioPlaybackConnector", nullptr); + if (hExistingWnd) + { + SetForegroundWindow(hExistingWnd); + PostMessageW(hExistingWnd, WM_SHOW_DEVICEPICKER_FROM_OTHER_INSTANCE, 0, 0); + } + else + { + TaskDialog(nullptr, nullptr, _(L"Information"), nullptr, _(L"Another instance is running, but its window could not be found."), TDCBF_OK_BUTTON, TD_WARNING_ICON, nullptr); + } + return EXIT_SUCCESS; + } + winrt::init_apartment(); bool supported = false; @@ -81,6 +107,8 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, PostMessageW(g_hWnd, WM_CONNECTDEVICE, 0, 0); + ShowInitialToastNotification(); + MSG msg; while (GetMessageW(&msg, nullptr, 0, 0)) { @@ -117,8 +145,49 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) SaveSettings(); } Shell_NotifyIconW(NIM_DELETE, &g_nid); + + if (g_hMutex) + { + ReleaseMutex(g_hMutex); + CloseHandle(g_hMutex); + g_hMutex = NULL; + } + PostQuitMessage(0); break; + case WM_SHOW_DEVICEPICKER_FROM_OTHER_INSTANCE: + { + using namespace winrt::Windows::UI::Popups; + RECT iconRect; + HRESULT hr = Shell_NotifyIconGetRect(&g_niid, &iconRect); + if (FAILED(hr)) + { + LOG_HR(hr); + ClientToScreen(hWnd, reinterpret_cast(&iconRect.left)); + ClientToScreen(hWnd, reinterpret_cast(&iconRect.right)); + } + + auto dpi = GetDpiForWindow(hWnd); + Rect rect = { + static_cast(iconRect.left * USER_DEFAULT_SCREEN_DPI / dpi), + static_cast(iconRect.top * USER_DEFAULT_SCREEN_DPI / dpi), + static_cast((iconRect.right - iconRect.left) * USER_DEFAULT_SCREEN_DPI / dpi), + static_cast((iconRect.bottom - iconRect.top) * USER_DEFAULT_SCREEN_DPI / dpi) + }; + if (IsRectEmpty(&iconRect) || FAILED(hr)) { + rect = { 100.0f, 100.0f, 300.0f, 400.0f }; + } + + SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), SWP_HIDEWINDOW); + ShowWindow(hWnd, SW_SHOW); + SetForegroundWindow(hWnd); + + if (g_devicePicker) + { + g_devicePicker.Show(rect, Placement::Above); + } + } + break; case WM_SETTINGCHANGE: if (lParam && CompareStringOrdinal(reinterpret_cast(lParam), -1, L"ImmersiveColorSet", -1, TRUE) == CSTR_EQUAL) { @@ -449,3 +518,61 @@ void UpdateNotifyIcon() } } } + +void ShowInitialToastNotification() +{ + try + { + std::wstring title = _(L"AudioPlaybackConnector"); + std::wstring message = _(L"Application has started and is running in the notification area."); + + std::wstring toastXmlString = + L"" + L"" + L"" + L"" + title + L"" + L"" + message + L"" + L"" + L"" + L""; + + XmlDocument toastXml; + toastXml.LoadXml(toastXmlString); + + ToastNotifier notifier{ nullptr }; + try { + notifier = ToastNotificationManager::CreateToastNotifier(); + } + catch (winrt::hresult_error) { + LOG_CAUGHT_EXCEPTION(); + wchar_t exePath[MAX_PATH]; + GetModuleFileNameW(NULL, exePath, MAX_PATH); + std::wstring appId = exePath; + try { + notifier = ToastNotificationManager::CreateToastNotifier(appId); + } + catch (winrt::hresult_error) { + LOG_CAUGHT_EXCEPTION(); + } + } + + if (!notifier) + { + return; + } + + ToastNotification toast(toastXml); + + using namespace std::chrono; + toast.ExpirationTime(winrt::Windows::Foundation::DateTime::clock::now() + seconds(5)); + + notifier.Show(toast); + } + catch (winrt::hresult_error) + { + + } + catch (std::exception) + { + } +} \ No newline at end of file diff --git a/AudioPlaybackConnector.h b/AudioPlaybackConnector.h index 3433674..c85efce 100644 --- a/AudioPlaybackConnector.h +++ b/AudioPlaybackConnector.h @@ -9,11 +9,17 @@ using namespace winrt::Windows::Media::Audio; using namespace winrt::Windows::UI::Xaml; using namespace winrt::Windows::UI::Xaml::Controls; using namespace winrt::Windows::UI::Xaml::Hosting; +using namespace winrt::Windows::UI::Notifications; +using namespace winrt::Windows::Data::Xml::Dom; namespace fs = std::filesystem; constexpr UINT WM_NOTIFYICON = WM_APP + 1; constexpr UINT WM_CONNECTDEVICE = WM_APP + 2; +constexpr UINT WM_SHOW_DEVICEPICKER_FROM_OTHER_INSTANCE = WM_APP + 3; +const WCHAR UNIQUE_MUTEX_NAME[] = L"{019730ef-fcc8-7f5a-94b3-8b77d764a65f}"; + +HANDLE g_hMutex = nullptr; HINSTANCE g_hInst; HWND g_hWnd; HWND g_hWndXaml; @@ -41,4 +47,4 @@ std::vector g_lastDevices; #include "Util.hpp" #include "I18n.hpp" #include "SettingsUtil.hpp" -#include "Direct2DSvg.hpp" +#include "Direct2DSvg.hpp" \ No newline at end of file diff --git a/AudioPlaybackConnector.vcxproj b/AudioPlaybackConnector.vcxproj index f52db62..f10d522 100644 --- a/AudioPlaybackConnector.vcxproj +++ b/AudioPlaybackConnector.vcxproj @@ -40,58 +40,58 @@ Win32Proj {2daaabdd-2402-4023-bc3e-b6e93fad567b} AudioPlaybackConnector - 10.0 + 10.0.22621.0 Application true - v142 + v143 Unicode Application true - v142 + v143 Unicode Application false - v142 + v143 true Unicode Application false - v142 + v143 true Unicode Application true - v142 + v143 Unicode Application true - v142 + v143 Unicode Application false - v142 + v143 true Unicode Application false - v142 + v143 true Unicode diff --git a/pch.h b/pch.h index a18fed8..84eec62 100644 --- a/pch.h +++ b/pch.h @@ -50,5 +50,7 @@ #include #include #include +#include +#include #endif //PCH_H