mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-01-03 06:13:40 +01:00
LoadingDialog: fix possible racecon (#2004)
This commit is contained in:
parent
82472ffc11
commit
861a688b89
2 changed files with 44 additions and 72 deletions
|
|
@ -8,6 +8,7 @@ using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using CheapLoc;
|
using CheapLoc;
|
||||||
|
|
||||||
|
|
@ -31,15 +32,13 @@ namespace Dalamud;
|
||||||
"StyleCop.CSharp.LayoutRules",
|
"StyleCop.CSharp.LayoutRules",
|
||||||
"SA1519:Braces should not be omitted from multi-line child statement",
|
"SA1519:Braces should not be omitted from multi-line child statement",
|
||||||
Justification = "Multiple fixed blocks")]
|
Justification = "Multiple fixed blocks")]
|
||||||
internal sealed unsafe class LoadingDialog
|
internal sealed class LoadingDialog
|
||||||
{
|
{
|
||||||
private readonly RollingList<string> logs = new(20);
|
private readonly RollingList<string> logs = new(20);
|
||||||
|
private readonly TaskCompletionSource<HWND> hwndTaskDialog = new();
|
||||||
|
|
||||||
private Thread? thread;
|
private Thread? thread;
|
||||||
private HWND hwndTaskDialog;
|
|
||||||
private DateTime firstShowTime;
|
private DateTime firstShowTime;
|
||||||
private State currentState = State.LoadingDalamud;
|
|
||||||
private bool canHide;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enum representing the state of the dialog.
|
/// Enum representing the state of the dialog.
|
||||||
|
|
@ -72,35 +71,13 @@ internal sealed unsafe class LoadingDialog
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the current state of the dialog.
|
/// Gets or sets the current state of the dialog.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public State CurrentState
|
public State CurrentState { get; set; } = State.LoadingDalamud;
|
||||||
{
|
|
||||||
get => this.currentState;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (this.currentState == value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.currentState = value;
|
|
||||||
this.UpdateMainInstructionText();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether the dialog can be hidden by the user.
|
/// Gets or sets a value indicating whether the dialog can be hidden by the user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <exception cref="InvalidOperationException">Thrown if called before the dialog has been created.</exception>
|
/// <exception cref="InvalidOperationException">Thrown if called before the dialog has been created.</exception>
|
||||||
public bool CanHide
|
public bool CanHide { get; set; }
|
||||||
{
|
|
||||||
get => this.canHide;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (this.canHide == value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.canHide = value;
|
|
||||||
this.UpdateButtonEnabled();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Show the dialog.
|
/// Show the dialog.
|
||||||
|
|
@ -110,7 +87,7 @@ internal sealed unsafe class LoadingDialog
|
||||||
if (IsGloballyHidden)
|
if (IsGloballyHidden)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (this.thread?.IsAlive == true)
|
if (this.thread is not null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.thread = new Thread(this.ThreadStart)
|
this.thread = new Thread(this.ThreadStart)
|
||||||
|
|
@ -126,22 +103,28 @@ internal sealed unsafe class LoadingDialog
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Hide the dialog.
|
/// Hide the dialog.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void HideAndJoin()
|
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||||
|
public async Task HideAndJoin()
|
||||||
{
|
{
|
||||||
IsGloballyHidden = true;
|
IsGloballyHidden = true;
|
||||||
if (this.thread?.IsAlive is not true)
|
if (this.hwndTaskDialog.TrySetCanceled() || this.hwndTaskDialog.Task.IsCanceled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SendMessageW(this.hwndTaskDialog, WM.WM_CLOSE, default, default);
|
try
|
||||||
this.thread.Join();
|
{
|
||||||
|
SendMessageW(await this.hwndTaskDialog.Task, WM.WM_CLOSE, default, default);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
this.thread?.Join();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateMainInstructionText()
|
private unsafe void UpdateMainInstructionText(HWND hwnd)
|
||||||
{
|
{
|
||||||
if (this.hwndTaskDialog == default)
|
fixed (void* pszText = this.CurrentState switch
|
||||||
return;
|
|
||||||
|
|
||||||
fixed (void* pszText = this.currentState switch
|
|
||||||
{
|
{
|
||||||
State.LoadingDalamud => Loc.Localize(
|
State.LoadingDalamud => Loc.Localize(
|
||||||
"LoadingDialogMainInstructionLoadingDalamud",
|
"LoadingDialogMainInstructionLoadingDalamud",
|
||||||
|
|
@ -156,18 +139,15 @@ internal sealed unsafe class LoadingDialog
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
SendMessageW(
|
SendMessageW(
|
||||||
this.hwndTaskDialog,
|
hwnd,
|
||||||
(uint)TASKDIALOG_MESSAGES.TDM_SET_ELEMENT_TEXT,
|
(uint)TASKDIALOG_MESSAGES.TDM_SET_ELEMENT_TEXT,
|
||||||
(WPARAM)(int)TASKDIALOG_ELEMENTS.TDE_MAIN_INSTRUCTION,
|
(WPARAM)(int)TASKDIALOG_ELEMENTS.TDE_MAIN_INSTRUCTION,
|
||||||
(LPARAM)pszText);
|
(LPARAM)pszText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateContentText()
|
private unsafe void UpdateContentText(HWND hwnd)
|
||||||
{
|
{
|
||||||
if (this.hwndTaskDialog == default)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var contentBuilder = new StringBuilder(
|
var contentBuilder = new StringBuilder(
|
||||||
Loc.Localize(
|
Loc.Localize(
|
||||||
"LoadingDialogContentInfo",
|
"LoadingDialogContentInfo",
|
||||||
|
|
@ -213,14 +193,14 @@ internal sealed unsafe class LoadingDialog
|
||||||
fixed (void* pszText = contentBuilder.ToString())
|
fixed (void* pszText = contentBuilder.ToString())
|
||||||
{
|
{
|
||||||
SendMessageW(
|
SendMessageW(
|
||||||
this.hwndTaskDialog,
|
hwnd,
|
||||||
(uint)TASKDIALOG_MESSAGES.TDM_SET_ELEMENT_TEXT,
|
(uint)TASKDIALOG_MESSAGES.TDM_SET_ELEMENT_TEXT,
|
||||||
(WPARAM)(int)TASKDIALOG_ELEMENTS.TDE_CONTENT,
|
(WPARAM)(int)TASKDIALOG_ELEMENTS.TDE_CONTENT,
|
||||||
(LPARAM)pszText);
|
(LPARAM)pszText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateExpandedInformation()
|
private unsafe void UpdateExpandedInformation(HWND hwnd)
|
||||||
{
|
{
|
||||||
const int maxCharactersPerLine = 80;
|
const int maxCharactersPerLine = 80;
|
||||||
|
|
||||||
|
|
@ -261,57 +241,51 @@ internal sealed unsafe class LoadingDialog
|
||||||
fixed (void* pszText = sb.ToString())
|
fixed (void* pszText = sb.ToString())
|
||||||
{
|
{
|
||||||
SendMessageW(
|
SendMessageW(
|
||||||
this.hwndTaskDialog,
|
hwnd,
|
||||||
(uint)TASKDIALOG_MESSAGES.TDM_SET_ELEMENT_TEXT,
|
(uint)TASKDIALOG_MESSAGES.TDM_SET_ELEMENT_TEXT,
|
||||||
(WPARAM)(int)TASKDIALOG_ELEMENTS.TDE_EXPANDED_INFORMATION,
|
(WPARAM)(int)TASKDIALOG_ELEMENTS.TDE_EXPANDED_INFORMATION,
|
||||||
(LPARAM)pszText);
|
(LPARAM)pszText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateButtonEnabled()
|
private void UpdateButtonEnabled(HWND hwnd) =>
|
||||||
{
|
SendMessageW(hwnd, (uint)TASKDIALOG_MESSAGES.TDM_ENABLE_BUTTON, IDOK, this.CanHide ? 1 : 0);
|
||||||
if (this.hwndTaskDialog == default)
|
|
||||||
return;
|
|
||||||
|
|
||||||
SendMessageW(this.hwndTaskDialog, (uint)TASKDIALOG_MESSAGES.TDM_ENABLE_BUTTON, IDOK, this.canHide ? 1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private HRESULT TaskDialogCallback(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam)
|
private HRESULT TaskDialogCallback(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam)
|
||||||
{
|
{
|
||||||
switch ((TASKDIALOG_NOTIFICATIONS)msg)
|
switch ((TASKDIALOG_NOTIFICATIONS)msg)
|
||||||
{
|
{
|
||||||
case TASKDIALOG_NOTIFICATIONS.TDN_CREATED:
|
case TASKDIALOG_NOTIFICATIONS.TDN_CREATED:
|
||||||
this.hwndTaskDialog = hwnd;
|
if (!this.hwndTaskDialog.TrySetResult(hwnd))
|
||||||
|
return E.E_FAIL;
|
||||||
|
|
||||||
this.UpdateMainInstructionText();
|
this.UpdateMainInstructionText(hwnd);
|
||||||
this.UpdateContentText();
|
this.UpdateContentText(hwnd);
|
||||||
this.UpdateExpandedInformation();
|
this.UpdateExpandedInformation(hwnd);
|
||||||
this.UpdateButtonEnabled();
|
this.UpdateButtonEnabled(hwnd);
|
||||||
SendMessageW(hwnd, (int)TASKDIALOG_MESSAGES.TDM_SET_PROGRESS_BAR_MARQUEE, 1, 0);
|
SendMessageW(hwnd, (int)TASKDIALOG_MESSAGES.TDM_SET_PROGRESS_BAR_MARQUEE, 1, 0);
|
||||||
|
|
||||||
// Bring to front
|
// Bring to front
|
||||||
|
ShowWindow(hwnd, SW.SW_SHOW);
|
||||||
SetWindowPos(hwnd, HWND.HWND_TOPMOST, 0, 0, 0, 0, SWP.SWP_NOSIZE | SWP.SWP_NOMOVE);
|
SetWindowPos(hwnd, HWND.HWND_TOPMOST, 0, 0, 0, 0, SWP.SWP_NOSIZE | SWP.SWP_NOMOVE);
|
||||||
SetWindowPos(hwnd, HWND.HWND_NOTOPMOST, 0, 0, 0, 0, SWP.SWP_NOSIZE | SWP.SWP_NOMOVE);
|
SetWindowPos(hwnd, HWND.HWND_NOTOPMOST, 0, 0, 0, 0, SWP.SWP_NOSIZE | SWP.SWP_NOMOVE);
|
||||||
ShowWindow(hwnd, SW.SW_SHOW);
|
|
||||||
SetForegroundWindow(hwnd);
|
SetForegroundWindow(hwnd);
|
||||||
SetFocus(hwnd);
|
SetFocus(hwnd);
|
||||||
SetActiveWindow(hwnd);
|
SetActiveWindow(hwnd);
|
||||||
return S.S_OK;
|
return S.S_OK;
|
||||||
|
|
||||||
case TASKDIALOG_NOTIFICATIONS.TDN_DESTROYED:
|
|
||||||
this.hwndTaskDialog = default;
|
|
||||||
return S.S_OK;
|
|
||||||
|
|
||||||
case TASKDIALOG_NOTIFICATIONS.TDN_TIMER:
|
case TASKDIALOG_NOTIFICATIONS.TDN_TIMER:
|
||||||
this.UpdateContentText();
|
this.UpdateMainInstructionText(hwnd);
|
||||||
this.UpdateExpandedInformation();
|
this.UpdateContentText(hwnd);
|
||||||
|
this.UpdateExpandedInformation(hwnd);
|
||||||
|
this.UpdateButtonEnabled(hwnd);
|
||||||
return S.S_OK;
|
return S.S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
return S.S_OK;
|
return S.S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThreadStart()
|
private unsafe void ThreadStart()
|
||||||
{
|
{
|
||||||
// We don't have access to the asset service here.
|
// We don't have access to the asset service here.
|
||||||
var workingDirectory = Service<Dalamud>.Get().StartInfo.WorkingDirectory;
|
var workingDirectory = Service<Dalamud>.Get().StartInfo.WorkingDirectory;
|
||||||
|
|
@ -386,7 +360,7 @@ internal sealed unsafe class LoadingDialog
|
||||||
|
|
||||||
gch = GCHandle.Alloc((Func<HWND, uint, WPARAM, LPARAM, HRESULT>)this.TaskDialogCallback);
|
gch = GCHandle.Alloc((Func<HWND, uint, WPARAM, LPARAM, HRESULT>)this.TaskDialogCallback);
|
||||||
taskDialogConfig.lpCallbackData = GCHandle.ToIntPtr(gch);
|
taskDialogConfig.lpCallbackData = GCHandle.ToIntPtr(gch);
|
||||||
TaskDialogIndirect(&taskDialogConfig, null, null, null).ThrowOnError();
|
TaskDialogIndirect(&taskDialogConfig, null, null, null);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -280,11 +280,8 @@ internal static class ServiceManager
|
||||||
|
|
||||||
Log.Error(e, "Failed resolving blocking services");
|
Log.Error(e, "Failed resolving blocking services");
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
loadingDialog.HideAndJoin();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
await loadingDialog.HideAndJoin();
|
||||||
return;
|
return;
|
||||||
|
|
||||||
async Task WaitWithTimeoutConsent(IEnumerable<Task> tasksEnumerable, LoadingDialog.State state)
|
async Task WaitWithTimeoutConsent(IEnumerable<Task> tasksEnumerable, LoadingDialog.State state)
|
||||||
|
|
@ -414,13 +411,14 @@ internal static class ServiceManager
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
BlockingServicesLoadedTaskCompletionSource.SetException(e);
|
BlockingServicesLoadedTaskCompletionSource.SetException(e);
|
||||||
loadingDialog.HideAndJoin();
|
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
// don't care, as this means task result/exception has already been set
|
// don't care, as this means task result/exception has already been set
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await loadingDialog.HideAndJoin();
|
||||||
|
|
||||||
while (tasks.Any())
|
while (tasks.Any())
|
||||||
{
|
{
|
||||||
await Task.WhenAny(tasks);
|
await Task.WhenAny(tasks);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue