Skip to content
This repository has been archived by the owner on Jul 5, 2024. It is now read-only.

Commit

Permalink
Add fallback progress dialog if there is no splash image
Browse files Browse the repository at this point in the history
  • Loading branch information
caesay committed Feb 21, 2022
1 parent 1fc9760 commit 43fded6
Show file tree
Hide file tree
Showing 8 changed files with 310 additions and 100 deletions.
85 changes: 85 additions & 0 deletions src/Update/ComposedWindow.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using System;
using System.Drawing;
using System.IO;
using Squirrel.SimpleSplat;
using Squirrel.Update.Windows;

namespace Squirrel.Update
{
internal class ComposedWindow : ISplashWindow
{
private readonly Bitmap _img;
private readonly Icon _icon;
private ISplashWindow _window;

public ComposedWindow(string appName, bool silent, byte[] iconBytes, byte[] splashBytes)
{
try {
// we only accept a byte array and convert to memorystream because
// gdi needs to seek and get length which is not supported in DeflateStream
if (iconBytes?.Length > 0) _icon = new Icon(new MemoryStream(iconBytes));
if (splashBytes?.Length > 0) _img = (Bitmap) Bitmap.FromStream(new MemoryStream(splashBytes));
} catch (Exception ex) {
this.Log().WarnException("Unable to load splash image", ex);
}

// don't bother creating a window if we're in silent mode, we can't show any UI
try {
if (!silent) {
if (_img != null) {
_window = new User32SplashWindow(appName, _icon, _img);
} else {
_window = new ComCtlProgressDialog(appName, _icon);
}
} else {
this.Log().Warn("Running install in SILENT mode, no prompts will be shown to the user. Dialogs will auto-respond as no/cancel.");
}
} catch (Exception ex) {
this.Log().ErrorException("Unable to open splash window", ex);
}
}

public void Dispose()
{
LogThrow("Failed to dispose window.", () => _window?.Dispose());
_img?.Dispose();
_icon?.Dispose();
}

public void Hide()
=> LogThrow("Failed to hide window.", () => _window?.Hide());

public void SetProgress(ulong completed, ulong total)
=> LogThrow("Failed to set progress.", () => _window?.SetProgress(completed, total));

public void SetProgressIndeterminate()
=> LogThrow("Failed to set progress indeterminate.", () => _window?.SetProgressIndeterminate());

public void Show()
=> LogThrow("Failed to show window.", () => _window?.Show());

public void ShowErrorDialog(string title, string message)
=> LogThrow("Failed to show error dialog.", () => _window?.ShowErrorDialog(title, message));

public void ShowInfoDialog(string title, string message)
=> LogThrow("Failed to show info dialog.", () => _window?.ShowInfoDialog(title, message));

public bool ShowQuestionDialog(string title, string message)
=> LogThrow("Failed to show question dialog.", () => _window?.ShowQuestionDialog(title, message) ?? false, false);

private void LogThrow(string msg, Action act)
{
try { act(); } catch (Exception ex) { this.Log().ErrorException(msg, ex); }
}

private T LogThrow<T>(string msg, Func<T> act, T errRet)
{
try {
return act();
} catch (Exception ex) {
this.Log().ErrorException(msg, ex);
return errRet;
}
}
}
}
4 changes: 2 additions & 2 deletions src/Update/ISplashWindow.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
using System;
using Squirrel.SimpleSplat;

namespace Squirrel.Update
{
internal interface ISplashWindow : IDisposable
internal interface ISplashWindow : IDisposable, IEnableLogger
{
void Show();
void Hide();
void SetNoProgress();
void SetProgressIndeterminate();
void SetProgress(ulong completed, ulong total);
void ShowErrorDialog(string title, string message);
Expand Down
2 changes: 1 addition & 1 deletion src/Update/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ static async Task Setup(string setupPath, bool silentInstall, bool checkInstall)
}

using var _t = Utility.WithTempDirectory(out var tempFolder);
ISplashWindow splash = new Windows.User32SplashWindow(appname, silentInstall, zp.SetupIconBytes, zp.SetupSplashBytes);
ISplashWindow splash = new ComposedWindow(appname, silentInstall, zp.SetupIconBytes, zp.SetupSplashBytes);

// verify that this package can be installed on this cpu architecture
if (AssemblyRuntimeInfo.Architecture == RuntimeCpu.X86 && zp.MachineArchitecture == RuntimeCpu.X64) {
Expand Down
2 changes: 2 additions & 0 deletions src/Update/Update.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<PackageReference Include="NLog" Version="5.0.0-rc2" />
<PackageReference Include="Vanara.PInvoke.User32" Version="3.3.15" />
<PackageReference Include="Vanara.PInvoke.SHCore" Version="3.3.15" />
<PackageReference Include="Vanara.PInvoke.ComCtl32" Version="3.3.15" />
</ItemGroup>

<ItemGroup>
Expand All @@ -35,6 +36,7 @@
<TrimmableAssembly Include="Vanara.PInvoke.Shared" />
<TrimmableAssembly Include="Vanara.PInvoke.SHCore" />
<TrimmableAssembly Include="Vanara.PInvoke.User32" />
<TrimmableAssembly Include="Vanara.PInvoke.ComCtl32" />
</ItemGroup>

<!-- Allow netframework binaries to be built explicitly using the command line -->
Expand Down
132 changes: 132 additions & 0 deletions src/Update/Windows/ComCtlProgressDialog.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
using System;
using System.Drawing;
using System.Threading;
using Squirrel.SimpleSplat;
using Vanara.PInvoke;
using Vanara.Windows.Forms;
using static Vanara.PInvoke.ComCtl32;
using static Vanara.PInvoke.User32;

namespace Squirrel.Update.Windows
{
internal class ComCtlProgressDialog : WindowBase
{
public override IntPtr Handle => _hwnd != HWND.NULL ? _hwnd.DangerousGetHandle() : IntPtr.Zero;
private HWND _hwnd;
private readonly Icon _icon;
private readonly object _lock = new object();
private readonly ManualResetEvent _signal;
private bool _marquee = true;
private bool _closing = false;

public ComCtlProgressDialog(string appName, Icon icon) : base(appName)
{
_signal = new ManualResetEvent(false);
_icon = icon;
}

public override void Hide()
{
lock (_lock) {
if (Handle == IntPtr.Zero) return;
_closing = true;
SendMessage(_hwnd, WindowMessage.WM_CLOSE);
}
}

public override void SetProgress(ulong completed, ulong total)
{
lock (_lock) {
if (Handle == IntPtr.Zero) return;
var progress = (int) Math.Round((double) completed / (double) total * 100);
if (_marquee) {
_marquee = false;
SendMessage(_hwnd, (uint) TaskDialogMessage.TDM_SET_PROGRESS_BAR_MARQUEE, (IntPtr) 0, (IntPtr) 0); // turn off marque animation
SendMessage(_hwnd, (uint) TaskDialogMessage.TDM_SET_MARQUEE_PROGRESS_BAR, (IntPtr) 0); // disable marque mode
}
SendMessage(_hwnd, (uint) TaskDialogMessage.TDM_SET_PROGRESS_BAR_POS, (IntPtr) progress);
}
}

public override void SetProgressIndeterminate()
{
lock (_lock) {
if (Handle == IntPtr.Zero) return;
if (!_marquee) {
_marquee = true;
SendMessage(_hwnd, (uint) TaskDialogMessage.TDM_SET_PROGRESS_BAR_POS, (IntPtr) 0); // clear current progress bar
SendMessage(_hwnd, (uint) TaskDialogMessage.TDM_SET_MARQUEE_PROGRESS_BAR, (IntPtr) 1); // enable marque mode
SendMessage(_hwnd, (uint) TaskDialogMessage.TDM_SET_PROGRESS_BAR_MARQUEE, (IntPtr) 1, (IntPtr) 0); // turn on marque animation
}
}
}

public override void Show()
{
lock (_lock) {
if (Handle != IntPtr.Zero) return;
_signal.Reset();
_closing = false;
_marquee = true;
var t = new Thread(ShowProc);
t.IsBackground = true;
t.SetApartmentState(ApartmentState.STA);
t.Start();
if (!_signal.WaitOne(5000)) {
throw new Exception("Timeout waiting for splash window to open");
}
}
}

private void ShowProc()
{
var config = new TASKDIALOGCONFIG();
config.dwCommonButtons = TASKDIALOG_COMMON_BUTTON_FLAGS.TDCBF_CANCEL_BUTTON;
config.MainInstruction = "Installing " + AppName;
//config.Content = "Hello this is some content";
config.pfCallbackProc = new TaskDialogCallbackProc(CallbackProc);
config.dwFlags = TASKDIALOG_FLAGS.TDF_SIZE_TO_CONTENT | TASKDIALOG_FLAGS.TDF_SHOW_PROGRESS_BAR
| TASKDIALOG_FLAGS.TDF_SHOW_MARQUEE_PROGRESS_BAR;

if (_icon != null) {
config.dwFlags |= TASKDIALOG_FLAGS.TDF_USE_HICON_MAIN;
config.mainIcon = _icon.Handle;
}

using (new ComCtl32v6Context()) {
var hr = TaskDialogIndirect(config, out var b1, out var b2, out var b3);
if (hr.Failed)
this.Log().ErrorException("Failed to open task dialog.", hr.GetException());
}
}

private HRESULT CallbackProc(HWND hwnd, TaskDialogNotification msg, IntPtr wParam, IntPtr lParam, IntPtr refData)
{
switch (msg) {

case TaskDialogNotification.TDN_DIALOG_CONSTRUCTED:
_hwnd = hwnd;
// Start marquee animation
SendMessage(hwnd, (uint) TaskDialogMessage.TDM_SET_PROGRESS_BAR_MARQUEE, (IntPtr) 1, (IntPtr) 0);
break;

case TaskDialogNotification.TDN_CREATED:
_signal.Set();
break;

case TaskDialogNotification.TDN_DESTROYED:
_hwnd = HWND.NULL;
break;

case TaskDialogNotification.TDN_BUTTON_CLICKED:
// TODO support cancellation?
return _closing ? 0 : 1;

}

return HRESULT.S_OK;
}

public override void Dispose() { }
}
}
4 changes: 2 additions & 2 deletions src/Update/Windows/ThreadDpiScalingContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

namespace Squirrel.Update.Windows
{
public enum ThreadScalingMode
internal enum ThreadScalingMode
{
Unaware,
SystemAware,
Expand All @@ -18,7 +18,7 @@ public enum ThreadScalingMode
UnawareGdiScaled,
}

public static class ThreadDpiScalingContext
internal static class ThreadDpiScalingContext
{
private static readonly DPI_AWARENESS_CONTEXT DPI_AWARENESS_CONTEXT_UNAWARE = new DPI_AWARENESS_CONTEXT((IntPtr)(-1));
private static readonly DPI_AWARENESS_CONTEXT DPI_AWARENESS_CONTEXT_SYSTEM_AWARE = new DPI_AWARENESS_CONTEXT((IntPtr)(-2));
Expand Down
Loading

0 comments on commit 43fded6

Please sign in to comment.