Skip to content

Commit

Permalink
feat: 消息窗口创建在单独的线程中
Browse files Browse the repository at this point in the history
一个线程内不能创建多个 XAML Islands 窗口
  • Loading branch information
Blinue committed Oct 15, 2024
1 parent 650c60f commit 378f81d
Show file tree
Hide file tree
Showing 17 changed files with 326 additions and 45 deletions.
19 changes: 5 additions & 14 deletions src/Magpie.App/App.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
#include "EffectsService.h"
#include "UpdateService.h"
#include "LocalizationService.h"
#include "Logger.h"
#include "ToastService.h"

namespace winrt::Magpie::App::implementation {

Expand All @@ -47,17 +47,7 @@ App::App() {
EffectsService::Get().StartInitialize();

// 初始化 XAML 框架
_windowsXamlManager = Hosting::WindowsXamlManager::InitializeForCurrentThread();

const bool isWin11 = Win32Utils::GetOSVersion().IsWin11();
if (!isWin11) {
// Win10 中隐藏 DesktopWindowXamlSource 窗口
if (CoreWindow coreWindow = CoreWindow::GetForCurrentThread()) {
HWND hwndDWXS;
coreWindow.as<ICoreWindowInterop>()->get_WindowHandle(&hwndDWXS);
ShowWindow(hwndDWXS, SW_HIDE);
}
}
_xamlManagerWrapper.emplace();

LocalizationService::Get().EarlyInitialize();
}
Expand All @@ -72,8 +62,7 @@ void App::Close() {
}
_isClosed = true;

_windowsXamlManager.Close();
_windowsXamlManager = nullptr;
_xamlManagerWrapper.reset();

Exit();
}
Expand All @@ -98,6 +87,7 @@ StartUpOptions App::Initialize(int) {
result.IsNeedElevated = settings.IsAlwaysRunAsAdmin();

LocalizationService::Get().Initialize();
ToastService::Get().Initialize();
ShortcutService::Get().Initialize();
ScalingService::Get().Initialize();
UpdateService::Get().Initialize();
Expand All @@ -110,6 +100,7 @@ void App::Uninitialize() {
// 不显示托盘图标的情况下关闭主窗口仍会在后台驻留数秒,推测和 XAML Islands 有关
// 这里提前取消热键注册,这样关闭 Magpie 后立即重新打开不会注册热键失败
ShortcutService::Get().Uninitialize();
ToastService::Get().Uninitialize();
}

bool App::IsShowNotifyIcon() const noexcept {
Expand Down
4 changes: 2 additions & 2 deletions src/Magpie.App/App.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#pragma once
#include "App.g.h"
#include <winrt/Windows.UI.Xaml.Hosting.h>
#include "XamlHostingHelper.h"

namespace winrt::Magpie::App::implementation {

Expand Down Expand Up @@ -44,7 +44,7 @@ class App : public App_base<App, Markup::IXamlMetadataProvider> {
void Restart() const noexcept;

private:
Hosting::WindowsXamlManager _windowsXamlManager{ nullptr };
std::optional<XamlHostingHelper::ManagerWrapper> _xamlManagerWrapper;
weak_ref<Magpie::App::RootPage> _rootPage{ nullptr };
HWND _hwndMain = NULL;
bool _isClosed = false;
Expand Down
19 changes: 19 additions & 0 deletions src/Magpie.App/Magpie.App.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -228,12 +228,18 @@
<DependentUpon>TitleBarControl.xaml</DependentUpon>
<SubType>Code</SubType>
</ClInclude>
<ClInclude Include="ToastPage.h">
<DependentUpon>ToastPage.xaml</DependentUpon>
<SubType>Code</SubType>
</ClInclude>
<ClInclude Include="ToastService.h" />
<ClInclude Include="TouchHelper.h" />
<ClInclude Include="UpdateService.h" />
<ClInclude Include="WrapPanel.h">
<DependentUpon>WrapPanel.idl</DependentUpon>
<SubType>Code</SubType>
</ClInclude>
<ClInclude Include="XamlHostingHelper.h" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
Expand Down Expand Up @@ -403,14 +409,24 @@
<DependentUpon>TitleBarControl.xaml</DependentUpon>
<SubType>Code</SubType>
</ClCompile>
<ClCompile Include="ToastPage.cpp">
<DependentUpon>ToastPage.xaml</DependentUpon>
<SubType>Code</SubType>
</ClCompile>
<ClCompile Include="ToastService.cpp" />
<ClCompile Include="TouchHelper.cpp" />
<ClCompile Include="UpdateService.cpp" />
<ClCompile Include="WrapPanel.cpp">
<DependentUpon>WrapPanel.idl</DependentUpon>
<SubType>Code</SubType>
</ClCompile>
<ClCompile Include="XamlHostingHelper.cpp" />
</ItemGroup>
<ItemGroup>
<Midl Include="ToastPage.idl">
<DependentUpon>ToastPage.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<None Include="SettingsExpanderCornerRadiusConverter.idl">
<SubType>Designer</SubType>
</None>
Expand Down Expand Up @@ -591,6 +607,9 @@
<Page Include="TitleBarControl.xaml">
<SubType>Designer</SubType>
</Page>
<Page Include="ToastPage.xaml">
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="Magpie.App.rc" />
Expand Down
15 changes: 15 additions & 0 deletions src/Magpie.App/Magpie.App.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@
<ClCompile Include="TouchHelper.cpp">
<Filter>Helpers</Filter>
</ClCompile>
<ClCompile Include="ToastService.cpp">
<Filter>Services</Filter>
</ClCompile>
<ClCompile Include="XamlHostingHelper.cpp">
<Filter>Helpers</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
Expand Down Expand Up @@ -123,6 +129,12 @@
<ClInclude Include="TouchHelper.h">
<Filter>Helpers</Filter>
</ClInclude>
<ClInclude Include="ToastService.h">
<Filter>Services</Filter>
</ClInclude>
<ClInclude Include="XamlHostingHelper.h">
<Filter>Helpers</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="Pages">
Expand Down Expand Up @@ -282,6 +294,9 @@
<Page Include="BlueInfoBar.xaml">
<Filter>Styles</Filter>
</Page>
<Page Include="ToastPage.xaml">
<Filter>Pages</Filter>
</Page>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="Magpie.App.rc" />
Expand Down
9 changes: 9 additions & 0 deletions src/Magpie.App/ToastPage.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include "pch.h"
#include "ToastPage.h"
#if __has_include("ToastPage.g.cpp")
#include "ToastPage.g.cpp"
#endif

namespace winrt::Magpie::App::implementation {

}
13 changes: 13 additions & 0 deletions src/Magpie.App/ToastPage.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once
#include "ToastPage.g.h"

namespace winrt::Magpie::App::implementation {
struct ToastPage : ToastPageT<ToastPage> {

};
}

namespace winrt::Magpie::App::factory_implementation {
struct ToastPage : ToastPageT<ToastPage, implementation::ToastPage> {
};
}
9 changes: 9 additions & 0 deletions src/Magpie.App/ToastPage.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Magpie.App {
[default_interface]
runtimeclass ToastPage : Windows.UI.Xaml.Controls.Page {
ToastPage();

// https://github.com/microsoft/microsoft-ui-xaml/issues/7579
void UnloadObject(Windows.UI.Xaml.DependencyObject object);
}
}
15 changes: 15 additions & 0 deletions src/Magpie.App/ToastPage.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Page x:Class="Magpie.App.ToastPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Magpie.App"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
mc:Ignorable="d">
<muxc:TeachingTip x:Name="MessageTeachingTip"
PreferredPlacement="Center"
ShouldConstrainToRootBounds="False">
<TextBlock x:Name="MessageTextBlock"
Text="test测试测试测试测试测试测试" />
</muxc:TeachingTip>
</Page>
113 changes: 113 additions & 0 deletions src/Magpie.App/ToastService.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#include "pch.h"
#include "ToastService.h"
#include "XamlHostingHelper.h"
#include "CommonSharedConstants.h"
#include "Utils.h"
#include <windows.ui.xaml.hosting.desktopwindowxamlsource.h>

using namespace winrt;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::UI::Xaml::Hosting;

namespace winrt::Magpie::App {

void ToastService::Initialize() noexcept {
_toastThread = std::thread(std::bind_front(&ToastService::_ToastThreadProc, this));
}

void ToastService::Uninitialize() noexcept {
if (!_toastThread.joinable()) {
return;
}

const HANDLE hToastThread = _toastThread.native_handle();

if (!wil::handle_wait(hToastThread, 0)) {
const DWORD threadId = GetThreadId(hToastThread);

// 持续尝试直到 _toastThread 创建了消息队列
while (!PostThreadMessage(threadId, CommonSharedConstants::WM_TOAST_QUIT, 0, 0)) {
if (wil::handle_wait(hToastThread, 1)) {
break;
}
}
}

_toastThread.join();
}

void ToastService::_ToastThreadProc() noexcept {
#ifdef _DEBUG
SetThreadDescription(GetCurrentThread(), L"Toast 线程");
#endif

winrt::init_apartment(winrt::apartment_type::single_threaded);

static Utils::Ignore _ = [] {
WNDCLASSEXW wcex{
.cbSize = sizeof(wcex),
.lpfnWndProc = DefWindowProc,
.hInstance = wil::GetModuleInstanceHandle(),
.lpszClassName = CommonSharedConstants::TOAST_WINDOW_CLASS_NAME
};
RegisterClassEx(&wcex);

return Utils::Ignore();
}();

// 创建窗口失败也应进入消息循环。Win10 中关闭任意线程的 DesktopWindowXamlSource 都会使主线程会崩溃,
// 在程序退出前,xamlSource 不能析构。见 https://github.com/microsoft/terminal/pull/15397
HWND hwndToast = CreateWindowEx(
WS_EX_NOREDIRECTIONBITMAP | WS_EX_TOOLWINDOW,
CommonSharedConstants::TOAST_WINDOW_CLASS_NAME,
L"Toast",
WS_POPUP | WS_VISIBLE,
200, 200, 0, 0,
NULL,
NULL,
wil::GetModuleInstanceHandle(),
nullptr
);

// DesktopWindowXamlSource 在控件之前创建则无需调用 WindowsXamlManager::InitializeForCurrentThread
DesktopWindowXamlSource xamlSource;
com_ptr<IDesktopWindowXamlSourceNative2> xamlSourceNative2 =
xamlSource.try_as<IDesktopWindowXamlSourceNative2>();

xamlSourceNative2->AttachToWindow(hwndToast);

HWND hwndXamlIsland;
xamlSourceNative2->get_WindowHandle(&hwndXamlIsland);
SetWindowPos(hwndXamlIsland, NULL, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_SHOWWINDOW);

ToastPage toastPage;
xamlSource.Content(toastPage);

auto tt = toastPage.FindName(L"MessageTeachingTip").as<MUXC::TeachingTip>();
tt.IsOpen(true);

MSG msg;
while (GetMessage(&msg, nullptr, 0, 0)) {
if (msg.message == CommonSharedConstants::WM_TOAST_QUIT) {
DestroyWindow(hwndToast);
break;
}

{
BOOL processed = FALSE;
HRESULT hr = xamlSourceNative2->PreTranslateMessage(&msg, &processed);
if (SUCCEEDED(hr) && processed) {
continue;
}
}

TranslateMessage(&msg);
DispatchMessage(&msg);
}

// 必须手动重置 Content,否则会内存泄露
xamlSource.Content(nullptr);
xamlSource.Close();
}

}
28 changes: 28 additions & 0 deletions src/Magpie.App/ToastService.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#pragma once
#include <winrt/Magpie.App.h>

namespace winrt::Magpie::App {

class ToastService {
public:
static ToastService& Get() noexcept {
static ToastService instance;
return instance;
}

ToastService(const ToastService&) = delete;
ToastService(ToastService&&) = delete;

void Initialize() noexcept;

void Uninitialize() noexcept;

private:
ToastService() = default;

void _ToastThreadProc() noexcept;

std::thread _toastThread;
};

}
39 changes: 39 additions & 0 deletions src/Magpie.App/XamlHostingHelper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#include "pch.h"
#include "XamlHostingHelper.h"
#include <CoreWindow.h>
#include "Win32Utils.h"

using namespace winrt;
using namespace Windows::UI::Xaml::Hosting;

namespace winrt::Magpie::App {

XamlHostingHelper::ManagerWrapper::ManagerWrapper() {
_windowsXamlManager = WindowsXamlManager::InitializeForCurrentThread();

if (!Win32Utils::GetOSVersion().IsWin11()) {
// Win10 中隐藏 DesktopWindowXamlSource 窗口
if (CoreWindow coreWindow = CoreWindow::GetForCurrentThread()) {
HWND hwndDWXS;
coreWindow.as<ICoreWindowInterop>()->get_WindowHandle(&hwndDWXS);
ShowWindow(hwndDWXS, SW_HIDE);
}
}
}

XamlHostingHelper::ManagerWrapper::~ManagerWrapper() {
if (!_windowsXamlManager) {
return;
}

_windowsXamlManager.Close();

// 做最后的清理,见
// https://learn.microsoft.com/en-us/uwp/api/windows.ui.xaml.hosting.windowsxamlmanager.close
MSG msg;
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
DispatchMessage(&msg);
}
}

}
Loading

0 comments on commit 378f81d

Please sign in to comment.