Skip to content

Commit 3737f1c

Browse files
authored
Feature/regression fixes (#190)
* Fix Paste button in Avalonia Browser app * Fix Attach disk image button in Avalonia Desktop and Browser app * Fix possible issue in Avalonia Desktop startup.
1 parent f31c60c commit 3737f1c

3 files changed

Lines changed: 68 additions & 52 deletions

File tree

src/apps/Avalonia/Highbyte.DotNet6502.App.Avalonia.Core/ViewModels/C64MenuViewModel.cs

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -683,12 +683,8 @@ private bool IsC64System()
683683

684684
// Events for View to handle clipboard and file operations
685685
public event EventHandler<string>? ClipboardCopyRequested;
686-
public event EventHandler? ClipboardPasteRequested;
687-
public event EventHandler? AttachDiskImageRequested;
688-
689-
// Properties for View to provide results
690-
public string? ClipboardPasteResult { get; set; }
691-
public byte[]? DiskImageFileResult { get; set; }
686+
public event EventHandler<TaskCompletionSource<string?>>? ClipboardPasteRequested;
687+
public event EventHandler<TaskCompletionSource<byte[]?>>? AttachDiskImageRequested;
692688

693689
private async Task RequestClipboardCopyAsync(string text)
694690
{
@@ -698,26 +694,28 @@ private async Task RequestClipboardCopyAsync(string text)
698694

699695
private async Task<string?> RequestClipboardPasteAsync()
700696
{
701-
ClipboardPasteResult = null;
702-
ClipboardPasteRequested?.Invoke(this, EventArgs.Empty);
703-
// View should synchronously set ClipboardPasteResult
704-
await Task.CompletedTask;
705-
return ClipboardPasteResult;
697+
if (ClipboardPasteRequested == null)
698+
return null;
699+
var tcs = new TaskCompletionSource<string?>();
700+
ClipboardPasteRequested.Invoke(this, tcs);
701+
return await tcs.Task;
706702
}
707703

708704
private async Task RequestAttachDiskImageAsync()
709705
{
710-
DiskImageFileResult = null;
711-
AttachDiskImageRequested?.Invoke(this, EventArgs.Empty);
712-
// View should handle file picker and set DiskImageFileResult
713-
await Task.Delay(100); // Give UI time to process
706+
if (AttachDiskImageRequested == null)
707+
return;
708+
709+
var tcs = new TaskCompletionSource<byte[]?>();
710+
AttachDiskImageRequested.Invoke(this, tcs);
711+
var fileBuffer = await tcs.Task;
714712

715-
if (DiskImageFileResult != null)
713+
if (fileBuffer != null)
716714
{
717715
try
718716
{
719717
// Parse the D64 disk image
720-
var d64DiskImage = Systems.Commodore64.TimerAndPeripheral.DiskDrive.D64.D64Parser.ParseD64File(DiskImageFileResult);
718+
var d64DiskImage = Systems.Commodore64.TimerAndPeripheral.DiskDrive.D64.D64Parser.ParseD64File(fileBuffer);
721719

722720
// Set the disk image on the running C64's DiskDrive1541
723721
var c64 = (C64)_avaloniaHostApp!.CurrentRunningSystem!;

src/apps/Avalonia/Highbyte.DotNet6502.App.Avalonia.Core/Views/C64MenuView.axaml.cs

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -67,54 +67,65 @@ private void OnClipboardCopyRequested(object? sender, string text)
6767
}
6868
});
6969

70-
private void OnClipboardPasteRequested(object? sender, EventArgs e)
70+
private void OnClipboardPasteRequested(object? sender, TaskCompletionSource<string?> tcs)
7171
=> SafeAsyncHelper.Execute(async () =>
7272
{
73-
if (ViewModel != null
74-
&& TopLevel.GetTopLevel(this) is { } topLevel
75-
&& topLevel.Clipboard is { } clipboard)
73+
if (TopLevel.GetTopLevel(this) is { } topLevel && topLevel.Clipboard is { } clipboard)
7674
{
7775
using var data = await clipboard.TryGetDataAsync();
78-
if (data is not null)
79-
ViewModel.ClipboardPasteResult = await data.TryGetTextAsync();
76+
tcs.SetResult(data is not null ? await data.TryGetTextAsync() : null);
77+
}
78+
else
79+
{
80+
tcs.SetResult(null);
8081
}
8182
});
8283

83-
private void OnAttachDiskImageRequested(object? sender, EventArgs e)
84+
private void OnAttachDiskImageRequested(object? sender, TaskCompletionSource<byte[]?> tcs)
8485
=> SafeAsyncHelper.Execute(async () =>
8586
{
86-
if (ViewModel == null || TopLevel.GetTopLevel(this) is not { } topLevel)
87+
if (TopLevel.GetTopLevel(this) is not { } topLevel)
88+
{
89+
tcs.SetResult(null);
8790
return;
91+
}
8892

8993
var storageProvider = topLevel.StorageProvider;
90-
if (storageProvider.CanOpen)
94+
if (!storageProvider.CanOpen)
9195
{
92-
var files = await storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
93-
{
94-
Title = "Select D64 Disk Image",
95-
AllowMultiple = false,
96-
FileTypeFilter = new[]
97-
{
98-
new FilePickerFileType("D64 Disk Images") { Patterns = new[] { "*.d64" } },
99-
new FilePickerFileType("All Files") { Patterns = new[] { "*" } }
100-
}
101-
});
96+
tcs.SetResult(null);
97+
return;
98+
}
10299

103-
if (files.Count > 0)
100+
var files = await storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
101+
{
102+
Title = "Select D64 Disk Image",
103+
AllowMultiple = false,
104+
FileTypeFilter = new[]
104105
{
105-
try
106-
{
107-
await using var stream = await files[0].OpenReadAsync();
108-
var fileBuffer = new byte[stream.Length];
109-
await stream.ReadExactlyAsync(fileBuffer);
106+
new FilePickerFileType("D64 Disk Images") { Patterns = new[] { "*.d64" } },
107+
new FilePickerFileType("All Files") { Patterns = new[] { "*" } }
108+
}
109+
});
110110

111-
ViewModel.DiskImageFileResult = fileBuffer;
112-
}
113-
catch (Exception ex)
114-
{
115-
Logger.LogError(ex, "Error reading disk image file");
116-
}
111+
if (files.Count > 0)
112+
{
113+
try
114+
{
115+
await using var stream = await files[0].OpenReadAsync();
116+
var fileBuffer = new byte[stream.Length];
117+
await stream.ReadExactlyAsync(fileBuffer);
118+
tcs.SetResult(fileBuffer);
117119
}
120+
catch (Exception ex)
121+
{
122+
Logger.LogError(ex, "Error reading disk image file");
123+
tcs.SetResult(null);
124+
}
125+
}
126+
else
127+
{
128+
tcs.SetResult(null);
118129
}
119130
});
120131

src/apps/Avalonia/Highbyte.DotNet6502.App.Avalonia.Desktop/Program.cs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -386,12 +386,19 @@ public static int Main(string[] args)
386386
var startupLogger = loggerFactory.CreateLogger(nameof(Program));
387387

388388
// The App object is created lazily by Avalonia during StartWithClassicDesktopLifetime,
389-
// so poll until both the App and HostApp instances have been initialized.
390-
startupLogger.LogInformation("Waiting for Avalonia HostApp initialization...");
391-
while (Core.App.Current is not { IsHostAppReady: true })
389+
// so we need App.Current to exist before we can await WhenHostAppReadyAsync.
390+
startupLogger.LogInformation("Waiting for Avalonia App instance...");
391+
while (Core.App.Current == null)
392392
await Task.Delay(10);
393393

394-
var hostApp = Core.App.Current.HostApp;
394+
// Await proper readiness — the TCS guarantees all writes made before TrySetResult
395+
// (including full HostApp construction) are visible to the continuation here.
396+
// VSTHRD003: suppressed — WhenHostAppReadyAsync is a TCS completion signal, not work
397+
// started in another context; ConfigureAwait(false) already handles context capture.
398+
startupLogger.LogInformation("Awaiting HostApp initialization...");
399+
#pragma warning disable VSTHRD003
400+
var hostApp = await Core.App.WhenHostAppReadyAsync.ConfigureAwait(false);
401+
#pragma warning restore VSTHRD003
395402
startupLogger.LogInformation("HostApp initialized.");
396403

397404
await AutomatedStartupHandler.ExecuteAsync(

0 commit comments

Comments
 (0)