-
Notifications
You must be signed in to change notification settings - Fork 4
/
Program.cs
177 lines (150 loc) · 6.46 KB
/
Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
using System.Drawing;
using System.Reflection;
using Microsoft.Web.WebView2.Core;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Gdi;
using Windows.Win32.UI.WindowsAndMessaging;
namespace MinimalWebView;
class Program
{
internal const uint WM_SYNCHRONIZATIONCONTEXT_WORK_AVAILABLE = PInvoke.WM_USER + 1;
private const string StaticFileDirectory = "wwwroot";
private static CoreWebView2Controller _controller;
private static UiThreadSynchronizationContext _uiThreadSyncCtx;
[STAThread]
static int Main(string[] args)
{
#if DEBUG // By default GUI apps have no console. Open one to enable Console.WriteLine debugging 🤠
PInvoke.AllocConsole();
#endif
HWND hwnd;
unsafe
{
HINSTANCE hInstance = PInvoke.GetModuleHandle((char*)null);
ushort classId;
HBRUSH backgroundBrush = PInvoke.CreateSolidBrush(0x271811); // this is actually #111827, Windows uses BBGGRR
if (backgroundBrush.IsNull)
{
// fallback to the system background color in case it fails
backgroundBrush = (HBRUSH)(IntPtr)(SYS_COLOR_INDEX.COLOR_BACKGROUND + 1);
}
fixed (char* classNamePtr = "MinimalWebView")
{
WNDCLASSW wc = new()
{
lpfnWndProc = WndProc,
lpszClassName = classNamePtr,
hInstance = hInstance,
hbrBackground = backgroundBrush,
style = WNDCLASS_STYLES.CS_VREDRAW | WNDCLASS_STYLES.CS_HREDRAW
};
classId = PInvoke.RegisterClass(wc);
if (classId == 0)
throw new Exception("class not registered");
}
fixed (char* windowNamePtr = $"MinimalWebView {Assembly.GetExecutingAssembly().GetName().Version}")
{
hwnd = PInvoke.CreateWindowEx(
0,
(char*)classId,
windowNamePtr,
WINDOW_STYLE.WS_OVERLAPPEDWINDOW,
PInvoke.CW_USEDEFAULT, PInvoke.CW_USEDEFAULT, 600, 500,
new HWND(),
new HMENU(),
hInstance,
null);
}
}
if (hwnd.Value == 0)
throw new Exception("hwnd not created");
PInvoke.ShowWindow(hwnd, SHOW_WINDOW_CMD.SW_NORMAL);
_uiThreadSyncCtx = new UiThreadSynchronizationContext(hwnd);
SynchronizationContext.SetSynchronizationContext(_uiThreadSyncCtx);
// Start initializing WebView2 in a fire-and-forget manner. Errors will be handled in the initialization function
_ = CreateCoreWebView2Async(hwnd);
Console.WriteLine("Starting message pump...");
MSG msg;
while (PInvoke.GetMessage(out msg, new HWND(), 0, 0))
{
PInvoke.TranslateMessage(msg);
PInvoke.DispatchMessage(msg);
}
return (int)msg.wParam.Value;
}
private static LRESULT WndProc(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case PInvoke.WM_SIZE:
OnSize(hwnd, wParam, GetLowWord(lParam.Value), GetHighWord(lParam.Value));
break;
case WM_SYNCHRONIZATIONCONTEXT_WORK_AVAILABLE:
_uiThreadSyncCtx.RunAvailableWorkOnCurrentThread();
break;
case PInvoke.WM_CLOSE:
PInvoke.PostQuitMessage(0);
break;
}
return PInvoke.DefWindowProc(hwnd, msg, wParam, lParam);
}
private static void OnSize(HWND hwnd, WPARAM wParam, int width, int height)
{
if (_controller != null)
_controller.Bounds = new Rectangle(0, 0, width, height);
}
private static async Task CreateCoreWebView2Async(HWND hwnd)
{
try
{
Console.WriteLine("Initializing WebView2...");
var environment = await CoreWebView2Environment.CreateAsync(null, null, null);
_controller = await environment.CreateCoreWebView2ControllerAsync(hwnd);
_controller.DefaultBackgroundColor = Color.Transparent; // avoids flash of white when page first renders
_controller.CoreWebView2.WebMessageReceived += CoreWebView2_WebMessageReceived;
_controller.CoreWebView2.SetVirtualHostNameToFolderMapping("minimalwebview.example", StaticFileDirectory, CoreWebView2HostResourceAccessKind.Allow);
PInvoke.GetClientRect(hwnd, out var hwndRect);
_controller.Bounds = new Rectangle(0, 0, hwndRect.right, hwndRect.bottom);
_controller.IsVisible = true;
_controller.CoreWebView2.Navigate("https://minimalwebview.example/index.html");
Console.WriteLine("WebView2 initialization succeeded.");
}
catch (WebView2RuntimeNotFoundException)
{
var result = PInvoke.MessageBox(hwnd, "WebView2 runtime not installed.", "Error", MESSAGEBOX_STYLE.MB_OK | MESSAGEBOX_STYLE.MB_ICONERROR);
if (result == MESSAGEBOX_RESULT.IDYES)
{
//TODO: download WV2 bootstrapper from https://go.microsoft.com/fwlink/p/?LinkId=2124703 and run it
}
Environment.Exit(1);
}
catch (Exception ex)
{
PInvoke.MessageBox(hwnd, $"Failed to initialize WebView2:{Environment.NewLine}{ex}", "Error", MESSAGEBOX_STYLE.MB_OK | MESSAGEBOX_STYLE.MB_ICONERROR);
Environment.Exit(1);
}
}
private static async void CoreWebView2_WebMessageReceived(object sender, CoreWebView2WebMessageReceivedEventArgs e)
{
var webMessage = e.TryGetWebMessageAsString();
if (string.IsNullOrEmpty(webMessage))
return;
// simulate moving some slow operation to a background thread
await Task.Run(() => Thread.Sleep(200));
// this will blow up if not run on the UI thread, so the SynchronizationContext needs to have been wired up correctly
await _controller.CoreWebView2.ExecuteScriptAsync($"alert('Hi from the UI thread! I got a message from the browser: {webMessage}')");
}
private static int GetLowWord(nint value)
{
uint xy = (uint)value;
int x = unchecked((short)xy);
return x;
}
private static int GetHighWord(nint value)
{
uint xy = (uint)value;
int y = unchecked((short)(xy >> 16));
return y;
}
}