├── App.xaml
├── App.xaml.cs
├── DemoDialog.xaml
├── DemoDialog.xaml.cs
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── WebDemoExe.csproj
├── icon.ico
├── icon_cables.ico
├── readme.md
└── webdemoexe.sln
/App.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
14 |
15 |
18 |
19 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/App.xaml.cs:
--------------------------------------------------------------------------------
1 | // Copyright (C) Microsoft Corporation. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | using System.Runtime.InteropServices;
6 | using System.Windows;
7 |
8 | namespace WebDemoExe
9 | {
10 | ///
11 | /// Interaction logic for App.xaml
12 | ///
13 | public partial class App : Application
14 | {
15 | public bool newRuntimeEventHandled = false;
16 |
17 | public App()
18 | {
19 | InitializeComponent();
20 | this.Resources["AdditionalArgs"] = "--enable-features=ThirdPartyStoragePartitioning,PartitionedCookies";
21 |
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/DemoDialog.xaml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
20 |
23 |
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 |
--------------------------------------------------------------------------------
/DemoDialog.xaml.cs:
--------------------------------------------------------------------------------
1 | // Copyright (C) Microsoft Corporation. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | using System.Diagnostics;
6 | using System.Drawing.Printing;
7 | using System.Runtime.InteropServices;
8 | using System.Windows;
9 |
10 | namespace WebDemoExe
11 | {
12 | ///
13 | /// Interaction logic for App.xaml
14 | ///
15 | public partial class DemoDialog :Window
16 | {
17 |
18 | public DemoDialog()
19 | {
20 | InitializeComponent();
21 | }
22 |
23 | private void okButton_Click(object sender, RoutedEventArgs e)
24 | {
25 | DialogResult = true;
26 |
27 | }
28 |
29 | private void fullscreen_Toggle(object sender, RoutedEventArgs e)
30 | {
31 | //Trace.WriteLine("toggle !!!", String(Fullscreen));
32 |
33 |
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
32 |
33 |
36 |
43 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | // Copyright (C) Microsoft Corporation. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | using System;
6 | using System.Collections.Generic;
7 | using System.ComponentModel;
8 | using System.Diagnostics;
9 | using System.Linq;
10 | using System.Text;
11 | using System.Windows;
12 | using System.Windows.Input;
13 | using System.IO;
14 | using Microsoft.Web.WebView2.Core;
15 | using Microsoft.Web.WebView2.Wpf;
16 | using System.Runtime.Remoting.Messaging;
17 | using System.Xml;
18 |
19 | namespace WebDemoExe
20 | {
21 | ///
22 | /// Interaction logic for MainWindow.xaml
23 | ///
24 | public partial class MainWindow : Window
25 | {
26 | CoreWebView2Environment _webViewEnvironment;
27 | CoreWebView2Environment WebViewEnvironment
28 | {
29 | get
30 | {
31 | if (_webViewEnvironment == null && webView?.CoreWebView2 != null)
32 | {
33 | _webViewEnvironment = webView.CoreWebView2.Environment;
34 | }
35 |
36 | return _webViewEnvironment;
37 | }
38 | }
39 |
40 | List _webViewFrames = new List();
41 |
42 | IDictionary<(string, CoreWebView2PermissionKind, bool), bool> _cachedPermissions =
43 | new Dictionary<(string, CoreWebView2PermissionKind, bool), bool>();
44 |
45 |
46 | public MainWindow()
47 | {
48 | var dlg = new DemoDialog();
49 |
50 |
51 | var reader = new XmlTextReader("webdemoexe.xml");
52 | reader.WhitespaceHandling = WhitespaceHandling.None;
53 |
54 | var currentTag = "";
55 | var dialogTitle = "webDemoExe";
56 | var autostart = false;
57 |
58 | try
59 | {
60 |
61 | while (reader.Read())
62 | {
63 | switch (reader.NodeType)
64 | {
65 | case XmlNodeType.Element:
66 | Trace.Write("ele", reader.Name);
67 | currentTag = reader.Name;
68 | break;
69 |
70 | case XmlNodeType.Text:
71 | if (currentTag.Equals("title")) dialogTitle = reader.Value;
72 | if (currentTag.Equals("autostart")) autostart = true;
73 | break;
74 |
75 | }
76 | }
77 |
78 | }
79 | catch (Exception e)
80 | {
81 | dialogTitle = "xml error";
82 |
83 | }
84 |
85 |
86 |
87 | if (autostart==false)
88 | {
89 | dlg.Title = dialogTitle;
90 |
91 | dlg.ShowDialog();
92 |
93 |
94 | Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--autoplay-policy=no-user-gesture-required");
95 | Environment.SetEnvironmentVariable("WEBVIEW2_USER_DATA_FOLDER", Path.GetTempPath());
96 |
97 | if (dlg.DialogResult == true)
98 | {
99 | }
100 | else
101 | {
102 | closeExe();
103 | return;
104 | }
105 | }
106 |
107 | DataContext = this;
108 | InitializeComponent();
109 | AttachControlEventHandlers(webView);
110 |
111 | if ((bool)dlg.Fullscreen.IsChecked)
112 | {
113 | this.WindowStyle = WindowStyle.None;
114 | this.Topmost = true;
115 | this.WindowState = WindowState.Maximized;
116 | }
117 |
118 | webView.Focus();
119 | }
120 |
121 | void AttachControlEventHandlers(WebView2 control)
122 | {
123 | //
124 | control.NavigationStarting += WebView_NavigationStarting;
125 | //
126 | //
127 | control.NavigationCompleted += WebView_NavigationCompleted;
128 | //
129 | control.CoreWebView2InitializationCompleted += WebView_CoreWebView2InitializationCompleted;
130 | control.KeyDown += WebView_KeyDown;
131 | }
132 |
133 |
134 | private bool _isControlInVisualTree = true;
135 |
136 | void RemoveControlFromVisualTree(WebView2 control)
137 | {
138 | Layout.Children.Remove(control);
139 | _isControlInVisualTree = false;
140 | }
141 |
142 | void AttachControlToVisualTree(WebView2 control)
143 | {
144 | Layout.Children.Add(control);
145 | _isControlInVisualTree = true;
146 | }
147 |
148 | WebView2 GetReplacementControl(bool useNewEnvironment)
149 | {
150 | WebView2 replacementControl = new WebView2();
151 | ((System.ComponentModel.ISupportInitialize)(replacementControl)).BeginInit();
152 | // Setup properties and bindings.
153 | if (useNewEnvironment)
154 | {
155 | // Create a new CoreWebView2CreationProperties instance so the environment
156 | // is made anew.
157 | replacementControl.CreationProperties = new CoreWebView2CreationProperties();
158 | replacementControl.CreationProperties.BrowserExecutableFolder = webView.CreationProperties.BrowserExecutableFolder;
159 | replacementControl.CreationProperties.AdditionalBrowserArguments = webView.CreationProperties.AdditionalBrowserArguments;
160 | shouldAttachEnvironmentEventHandlers = true;
161 | }
162 | else
163 | {
164 | replacementControl.CreationProperties = webView.CreationProperties;
165 | }
166 |
167 | AttachControlEventHandlers(replacementControl);
168 | replacementControl.Source = webView.Source ?? new Uri("https://www.bing.com");
169 | ((System.ComponentModel.ISupportInitialize)(replacementControl)).EndInit();
170 |
171 | return replacementControl;
172 | }
173 |
174 | void WebView_ProcessFailed(object sender, CoreWebView2ProcessFailedEventArgs e)
175 | {
176 | void ReinitIfSelectedByUser(string caption, string message)
177 | {
178 | this.Dispatcher.InvokeAsync(() =>
179 | {
180 | var selection = MessageBox.Show(message, caption, MessageBoxButton.YesNo);
181 | if (selection == MessageBoxResult.Yes)
182 | {
183 | // The control cannot be re-initialized so we setup a new instance to replace it.
184 | // Note the previous instance of the control is disposed of and removed from the
185 | // visual tree before attaching the new one.
186 | if (_isControlInVisualTree)
187 | {
188 | RemoveControlFromVisualTree(webView);
189 | }
190 | webView.Dispose();
191 | webView = GetReplacementControl(false);
192 | AttachControlToVisualTree(webView);
193 | // Set background transparent
194 | webView.DefaultBackgroundColor = System.Drawing.Color.Transparent;
195 | }
196 | });
197 | }
198 |
199 | void ReloadIfSelectedByUser(string caption, string message)
200 | {
201 | this.Dispatcher.InvokeAsync(() =>
202 | {
203 | var selection = MessageBox.Show(message, caption, MessageBoxButton.YesNo);
204 | if (selection == MessageBoxResult.Yes)
205 | {
206 | webView.Reload();
207 | // Set background transparent
208 | webView.DefaultBackgroundColor = System.Drawing.Color.Transparent;
209 | }
210 | });
211 | }
212 |
213 | bool IsAppContentUri(Uri source)
214 | {
215 | // Sample virtual host name for the app's content.
216 | // See CoreWebView2.SetVirtualHostNameToFolderMapping: https://learn.microsoft.com/dotnet/api/microsoft.web.webview2.core.corewebview2.setvirtualhostnametofoldermapping
217 | return source.Host == "appassets.example";
218 | }
219 |
220 | if (e.ProcessFailedKind == CoreWebView2ProcessFailedKind.FrameRenderProcessExited)
221 | {
222 | // A frame-only renderer has exited unexpectedly. Check if reload is needed.
223 | // In this sample we only reload if the app's content has been impacted.
224 | foreach (CoreWebView2FrameInfo frameInfo in e.FrameInfosForFailedProcess)
225 | {
226 | if (IsAppContentUri(new System.Uri(frameInfo.Source)))
227 | {
228 | System.Threading.SynchronizationContext.Current.Post((_) =>
229 | {
230 | ReloadIfSelectedByUser("App content frame unresponsive",
231 | "Browser render process for app frame exited unexpectedly. Reload page?");
232 | }, null);
233 | }
234 | }
235 |
236 | return;
237 | }
238 |
239 | // Show the process failure details. Apps can collect info for their logging purposes.
240 | this.Dispatcher.InvokeAsync(() =>
241 | {
242 | StringBuilder messageBuilder = new StringBuilder();
243 | messageBuilder.AppendLine($"Process kind: {e.ProcessFailedKind}");
244 | messageBuilder.AppendLine($"Reason: {e.Reason}");
245 | messageBuilder.AppendLine($"Exit code: {e.ExitCode}");
246 | messageBuilder.AppendLine($"Process description: {e.ProcessDescription}");
247 | MessageBox.Show(messageBuilder.ToString(), "Child process failed", MessageBoxButton.OK);
248 | });
249 |
250 | if (e.ProcessFailedKind == CoreWebView2ProcessFailedKind.BrowserProcessExited)
251 | {
252 | ReinitIfSelectedByUser("Browser process exited",
253 | "Browser process exited unexpectedly. Recreate webview?");
254 | }
255 | else if (e.ProcessFailedKind == CoreWebView2ProcessFailedKind.RenderProcessUnresponsive)
256 | {
257 | ReinitIfSelectedByUser("Web page unresponsive",
258 | "Browser render process has stopped responding. Recreate webview?");
259 | }
260 | else if (e.ProcessFailedKind == CoreWebView2ProcessFailedKind.RenderProcessExited)
261 | {
262 | ReloadIfSelectedByUser("Web page unresponsive",
263 | "Browser render process exited unexpectedly. Reload page?");
264 | }
265 | }
266 |
267 |
268 | void WebView_KeyDown(object sender, KeyEventArgs e)
269 | {
270 | if (e.IsRepeat) return;
271 |
272 | if (e.KeyboardDevice.IsKeyDown(Key.Escape))
273 | {
274 | closeExe();
275 | }
276 |
277 | /* bool ctrl = e.KeyboardDevice.IsKeyDown(Key.LeftCtrl) || e.KeyboardDevice.IsKeyDown(Key.RightCtrl);
278 | bool alt = e.KeyboardDevice.IsKeyDown(Key.LeftAlt) || e.KeyboardDevice.IsKeyDown(Key.RightAlt);
279 | bool shift = e.KeyboardDevice.IsKeyDown(Key.LeftShift) || e.KeyboardDevice.IsKeyDown(Key.RightShift);
280 | */ /*
281 | if (e.Key == Key.N && ctrl && !alt && !shift)
282 | {
283 | new MainWindow().Show();
284 | e.Handled = true;
285 | }
286 | else if (e.Key == Key.W && ctrl && !alt && !shift)
287 | {
288 | closeExe();
289 | e.Handled = true;
290 | }
291 | */
292 | }
293 |
294 | void WebView_NavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs e)
295 | {
296 | RequeryCommands();
297 | }
298 |
299 | void WebView_NavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e)
300 | {
301 | RequeryCommands();
302 | }
303 |
304 | private bool shouldAttachEnvironmentEventHandlers = true;
305 |
306 | private string GetSdkBuildVersion()
307 | {
308 | CoreWebView2EnvironmentOptions options = new CoreWebView2EnvironmentOptions();
309 |
310 | // The full version string A.B.C.D
311 | var targetVersionMajorAndRest = options.TargetCompatibleBrowserVersion;
312 | var versionList = targetVersionMajorAndRest.Split('.');
313 | if (versionList.Length != 4)
314 | {
315 | return "Invalid SDK build version";
316 | }
317 | // Keep C.D
318 | return versionList[2] + "." + versionList[3];
319 | }
320 |
321 | private string GetRuntimeVersion(CoreWebView2 webView2)
322 | {
323 | return webView2.Environment.BrowserVersionString;
324 | }
325 |
326 | private string GetAppPath()
327 | {
328 | return System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
329 | }
330 |
331 | private string GetRuntimePath(CoreWebView2 webView2)
332 | {
333 | int processId = (int)webView2.BrowserProcessId;
334 | try
335 | {
336 | Process process = System.Diagnostics.Process.GetProcessById(processId);
337 | var fileName = process.MainModule.FileName;
338 | return System.IO.Path.GetDirectoryName(fileName);
339 | }
340 | catch (ArgumentException e)
341 | {
342 | return e.Message;
343 | }
344 | catch (InvalidOperationException e)
345 | {
346 | return e.Message;
347 | }
348 | // Occurred when a 32-bit process wants to access the modules of a 64-bit process.
349 | catch (Win32Exception e)
350 | {
351 | return e.Message;
352 | }
353 | }
354 |
355 | private string GetStartPageUri(CoreWebView2 webView2)
356 | {
357 | string uri = "https://appassets.example/index.html";
358 | if (webView2 == null)
359 | {
360 | return uri;
361 | }
362 | string sdkBuildVersion = GetSdkBuildVersion(),
363 | runtimeVersion = GetRuntimeVersion(webView2),
364 | appPath = GetAppPath(),
365 | runtimePath = GetRuntimePath(webView2);
366 | string newUri = $"{uri}?sdkBuild={sdkBuildVersion}&runtimeVersion={runtimeVersion}" +
367 | $"&appPath={appPath}&runtimePath={runtimePath}";
368 | return newUri;
369 | }
370 |
371 | void WebView_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e)
372 | {
373 | if (e.IsSuccess)
374 | {
375 | // Setup host resource mapping for local files
376 | webView.CoreWebView2.SetVirtualHostNameToFolderMapping("appassets.example", "demo", CoreWebView2HostResourceAccessKind.DenyCors);
377 | // Set StartPage Uri
378 | webView.Source = new Uri(GetStartPageUri(webView.CoreWebView2));
379 |
380 | //
381 | webView.CoreWebView2.ProcessFailed += WebView_ProcessFailed;
382 | //
383 | //
384 | webView.CoreWebView2.DocumentTitleChanged += WebView_DocumentTitleChanged;
385 | //
386 | //
387 | //webView.CoreWebView2.IsDocumentPlayingAudioChanged += WebView_IsDocumentPlayingAudioChanged;
388 | //
389 | //
390 | //webView.CoreWebView2.IsMutedChanged += WebView_IsMutedChanged;
391 | //
392 | //
393 | webView.CoreWebView2.PermissionRequested += WebView_PermissionRequested;
394 | //
395 | //webView.CoreWebView2.DOMContentLoaded += WebView_PermissionManager_DOMContentLoaded;
396 | //webView.CoreWebView2.WebMessageReceived += WebView_PermissionManager_WebMessageReceived;
397 |
398 | // The CoreWebView2Environment instance is reused when re-assigning CoreWebView2CreationProperties
399 | // to the replacement control. We don't need to re-attach the event handlers unless the environment
400 | // instance has changed.
401 | if (shouldAttachEnvironmentEventHandlers)
402 | {
403 | try
404 | {
405 | //
406 | WebViewEnvironment.BrowserProcessExited += Environment_BrowserProcessExited;
407 | //
408 | //
409 | WebViewEnvironment.NewBrowserVersionAvailable += Environment_NewBrowserVersionAvailable;
410 | //
411 | //
412 | //WebViewEnvironment.ProcessInfosChanged += WebView_ProcessInfosChanged;
413 | //
414 | }
415 | catch (NotImplementedException)
416 | {
417 |
418 | }
419 | shouldAttachEnvironmentEventHandlers = false;
420 | }
421 |
422 | webView.CoreWebView2.FrameCreated += WebView_HandleIFrames;
423 |
424 | return;
425 | }
426 |
427 | // ERROR_DELETE_PENDING(0x8007012f)
428 | if (e.InitializationException.HResult == -2147024593)
429 | {
430 | MessageBox.Show($"Failed to create webview, because the profile's name has been marked as deleted, please use a different profile's name.");
431 | closeExe();
432 | return;
433 | }
434 | MessageBox.Show($"WebView2 creation failed with exception = {e.InitializationException}");
435 | }
436 |
437 | //
438 | private bool shouldAttemptReinitOnBrowserExit = false;
439 |
440 | void Environment_BrowserProcessExited(object sender, CoreWebView2BrowserProcessExitedEventArgs e)
441 | {
442 | // Let ProcessFailed handler take care of process failure.
443 | if (e.BrowserProcessExitKind == CoreWebView2BrowserProcessExitKind.Failed)
444 | {
445 | return;
446 | }
447 | if (shouldAttemptReinitOnBrowserExit)
448 | {
449 | _webViewEnvironment = null;
450 | webView = GetReplacementControl(true);
451 | AttachControlToVisualTree(webView);
452 | shouldAttemptReinitOnBrowserExit = false;
453 | }
454 | }
455 | //
456 |
457 | void WebView_HandleIFrames(object sender, CoreWebView2FrameCreatedEventArgs args)
458 | {
459 | _webViewFrames.Add(args.Frame);
460 | args.Frame.Destroyed += WebViewFrames_DestoryedNestedIFrames;
461 | }
462 | void WebViewFrames_DestoryedNestedIFrames(object sender, object args)
463 | {
464 | var frameToRemove = _webViewFrames.SingleOrDefault(r => r.IsDestroyed() == 1);
465 | if (frameToRemove != null)
466 | _webViewFrames.Remove(frameToRemove);
467 | }
468 |
469 | //
470 | // A new version of the WebView2 Runtime is available, our handler gets called.
471 | // We close our WebView and set a handler to reinitialize it once the WebView2
472 | // Runtime collection of processes are gone, so we get the new version of the
473 | // WebView2 Runtime.
474 | void Environment_NewBrowserVersionAvailable(object sender, object e)
475 | {
476 | if (((App)Application.Current).newRuntimeEventHandled)
477 | {
478 | return;
479 | }
480 |
481 | ((App)Application.Current).newRuntimeEventHandled = true;
482 | System.Threading.SynchronizationContext.Current.Post((_) =>
483 | {
484 | UpdateIfSelectedByUser();
485 | }, null);
486 | }
487 | //
488 |
489 | void UpdateIfSelectedByUser()
490 | {
491 | // New browser version available, ask user to close everything and re-init.
492 | StringBuilder messageBuilder = new StringBuilder(256);
493 | messageBuilder.Append("We detected there is a new version of the WebView2 Runtime installed. ");
494 | messageBuilder.Append("Do you want to switch to it now? This will re-create the WebView.");
495 | var selection = MessageBox.Show(this, messageBuilder.ToString(), "New WebView2 Runtime detected", MessageBoxButton.YesNo);
496 | if (selection == MessageBoxResult.Yes)
497 | {
498 | // If this or any other application creates additional WebViews from the same
499 | // environment configuration, all those WebViews need to be closed before
500 | // the browser process will exit. This sample creates a single WebView per
501 | // MainWindow, we let each MainWindow prepare to recreate and close its WebView.
502 | CloseAppWebViewsForUpdate();
503 | }
504 | ((App)Application.Current).newRuntimeEventHandled = false;
505 | }
506 |
507 | private void CloseAppWebViewsForUpdate()
508 | {
509 | foreach (Window window in Application.Current.Windows)
510 | {
511 | if (window is MainWindow mainWindow)
512 | {
513 | mainWindow.CloseWebViewForUpdate();
514 | }
515 | }
516 | }
517 |
518 | private void CloseWebViewForUpdate()
519 | {
520 | // We dispose of the control so the internal WebView objects are released
521 | // and the associated browser process exits.
522 | shouldAttemptReinitOnBrowserExit = true;
523 | RemoveControlFromVisualTree(webView);
524 | webView.Dispose();
525 | }
526 |
527 |
528 |
529 |
530 | private void sourceChanged(object sender, CoreWebView2SourceChangedEventArgs e)
531 | {
532 |
533 | if (webView.Source.AbsoluteUri.Contains("webdemoexe_exit")) closeExe();
534 | }
535 |
536 |
537 | void RequeryCommands()
538 | {
539 | // Seems like there should be a way to bind CanExecute directly to a bool property
540 | // so that the binding can take care keeping CanExecute up-to-date when the property's
541 | // value changes, but apparently there isn't. Instead we listen for the WebView events
542 | // which signal that one of the underlying bool properties might have changed and
543 | // bluntly tell all commands to re-check their CanExecute status.
544 | //
545 | // Another way to trigger this re-check would be to create our own bool dependency
546 | // properties on this class, bind them to the underlying properties, and implement a
547 | // PropertyChangedCallback on them. That arguably more directly binds the status of
548 | // the commands to the WebView's state, but at the cost of having an extraneous
549 | // dependency property sitting around for each underlying property, which doesn't seem
550 | // worth it, especially given that the WebView API explicitly documents which events
551 | // signal the property value changes.
552 | CommandManager.InvalidateRequerySuggested();
553 | }
554 |
555 | void WebView_DocumentTitleChanged(object sender, object e)
556 | {
557 | //
558 | this.Title = webView.CoreWebView2.DocumentTitle;
559 | //
560 | }
561 |
562 |
563 | //
564 | void WebView_PermissionRequested(object sender, CoreWebView2PermissionRequestedEventArgs args)
565 | {
566 | // allow everything!
567 | args.State = CoreWebView2PermissionState.Allow;
568 | }
569 | //
570 |
571 |
572 | void closeExe()
573 | {
574 | webView.Source = new Uri("about:blank");
575 | webView.Dispose();
576 | Close();
577 | System.Environment.Exit(1);
578 | }
579 | }
580 | }
581 |
--------------------------------------------------------------------------------
/WebDemoExe.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | WinExe
4 | net48
5 | true
6 | Hybrid Application Team
7 | holon
8 | WebDemoExe
9 | true
10 | Debug;Release
11 | icon.ico
12 |
13 |
14 | x64
15 |
16 |
17 | x86
18 |
19 |
20 | AnyCPU
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | Always
35 |
36 |
37 | Always
38 |
39 |
40 | Always
41 |
42 |
43 | Always
44 |
45 |
46 | Always
47 |
48 |
49 | Always
50 |
51 |
52 | Always
53 |
54 |
55 | Always
56 |
57 |
58 | Always
59 |
60 |
61 | Always
62 |
63 |
64 | Always
65 |
66 |
67 | Always
68 |
69 |
70 | Always
71 |
72 |
73 | Always
74 |
75 |
76 | Always
77 |
78 |
79 | Always
80 |
81 |
82 |
--------------------------------------------------------------------------------
/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pandrr/WebDemoExe/f18f3ea7b85f4c0309461063a6020663f0f72987/icon.ico
--------------------------------------------------------------------------------
/icon_cables.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pandrr/WebDemoExe/f18f3ea7b85f4c0309461063a6020663f0f72987/icon_cables.ico
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ## webDemoExe
4 |
5 | wrap your web demo into a windows exe format, just like a native demo.
6 |
7 | [download](https://github.com/pandrr/WebDemoExe/releases)
8 |
9 | ### about
10 |
11 | - it does not use electron, size is ~0.5mb
12 | - it will use edge to display your demo. edge is a chromium based browser, which is basically chrome
13 | - shows a little start dialog (only fullscreen option right now, more in the future hopefully)
14 |
15 | ### how
16 |
17 | - download the zip file from the releases section
18 | - put your static html/js files into the demo subfolder
19 | - edit webdemoexe.xml and change the title
20 | - rename webdemoexe.exe to your demo name
21 | - add `` into the config to not show the dialog at all and start directly, don't do this if you want to play audio without having another user interaction!
22 |
23 | - if the url contains "webdemoexe_exit" it will exit, e.g. use window.location.hash="webdemoexe_exit"
24 |
25 | ### technical
26 | - exe is not signed, still have to click "run anyway", like with most demos
27 | - webdemoexe uses [webview2](https://learn.microsoft.com/en-us/microsoft-edge/webview2/) and creates a virtual host from the demo subfolder to run your demo
28 | - escape to close is handled by webdemoexe
29 | - no gesture is needed to auto play audio, if you normally display a play button, make sure it only shows when audiocontext stats is not "running"...
30 |
31 | ### ideas
32 | - currently has no resolution selection, not sure how this is possible with wpf etc.
33 | - in the future the dialog could show link to website/online version and maybe a little teaser image...
34 | - there should be way to exit the app from js / in electron we always used `window.close()` not possible with webview2 afaik
35 |
36 | ### misc
37 |
38 | thanks to kb for helping with initial setup!
39 |
40 | any help is appreciated. i am not a windows developer, i hope everything here is not too wrong.
41 |
42 |
--------------------------------------------------------------------------------
/webdemoexe.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.5.33516.290
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebDemoExe", "WebDemoExe.csproj", "{EE405166-276B-486B-A7C6-D3E5BE2BBB6C}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {EE405166-276B-486B-A7C6-D3E5BE2BBB6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {EE405166-276B-486B-A7C6-D3E5BE2BBB6C}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {EE405166-276B-486B-A7C6-D3E5BE2BBB6C}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {EE405166-276B-486B-A7C6-D3E5BE2BBB6C}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {D67C87AC-0755-417B-837B-CF60986F29DF}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------