diff --git a/src/apps/Highbyte.DotNet6502.App.SadConsole/ConfigUI/C64MenuConsole.cs b/src/apps/Highbyte.DotNet6502.App.SadConsole/ConfigUI/C64MenuConsole.cs
index 47417266..cb52737e 100644
--- a/src/apps/Highbyte.DotNet6502.App.SadConsole/ConfigUI/C64MenuConsole.cs
+++ b/src/apps/Highbyte.DotNet6502.App.SadConsole/ConfigUI/C64MenuConsole.cs
@@ -5,6 +5,7 @@
using SadRogue.Primitives;
using Microsoft.Extensions.Logging;
using Highbyte.DotNet6502.Utils;
+using TextCopy;
namespace Highbyte.DotNet6502.App.SadConsole.ConfigUI;
public class C64MenuConsole : ControlsConsole
@@ -60,16 +61,27 @@ private void DrawUIItems()
Controls.Add(c64SaveBasicButton);
+ // Save Basic
+ var c64PasteTextButton = new Button("Paste")
+ {
+ Name = "c64PasteTextButton",
+ Position = (1, c64SaveBasicButton.Bounds.MaxExtentY + 2),
+ };
+ c64PasteTextButton.Click += C64PasteTextButton_Click;
+ Controls.Add(c64PasteTextButton);
+
+
// Config
var c64ConfigButton = new Button("C64 Config")
{
Name = "c64ConfigButton",
- Position = (1, c64SaveBasicButton.Bounds.MaxExtentY + 2),
+ Position = (1, c64PasteTextButton.Bounds.MaxExtentY + 2),
};
c64ConfigButton.Click += C64ConfigButton_Click;
Controls.Add(c64ConfigButton);
- var validationMessageValueLabel = CreateLabelValue(new string(' ', 20), 1, c64ConfigButton.Bounds.MaxExtentY + 2, "validationMessageValueLabel");
+
+ var validationMessageValueLabel = CreateLabelValue(new string(' ', 20), 1, c64PasteTextButton.Bounds.MaxExtentY + 2, "validationMessageValueLabel");
validationMessageValueLabel.TextColor = Controls.GetThemeColors().Red;
// Helper function to create a label and add it to the console
@@ -201,6 +213,15 @@ private void C64ConfigButton_Click(object sender, EventArgs e)
window.Show(true);
}
+ private void C64PasteTextButton_Click(object sender, EventArgs e)
+ {
+ var c64 = (C64)_sadConsoleHostApp.CurrentRunningSystem!;
+ var text = ClipboardService.GetText();
+ if (string.IsNullOrEmpty(text))
+ return;
+ c64.TextPaste.Paste(text);
+ }
+
protected override void OnIsDirtyChanged()
{
if (IsDirty)
@@ -215,8 +236,11 @@ private void SetControlStates()
var c64SaveBasicButton = Controls["c64SaveBasicButton"];
c64SaveBasicButton.IsEnabled = _sadConsoleHostApp.EmulatorState != Systems.EmulatorState.Uninitialized;
- var systemComboBox = Controls["c64ConfigButton"];
- systemComboBox.IsEnabled = _sadConsoleHostApp.EmulatorState == Systems.EmulatorState.Uninitialized;
+ var c64ConfigButton = Controls["c64ConfigButton"];
+ c64ConfigButton.IsEnabled = _sadConsoleHostApp.EmulatorState == Systems.EmulatorState.Uninitialized;
+
+ var c64PasteTextButton = Controls["c64PasteTextButton"];
+ c64PasteTextButton.IsEnabled = _sadConsoleHostApp.EmulatorState == Systems.EmulatorState.Running;
var validationMessageValueLabel = Controls["validationMessageValueLabel"] as Label;
(var isOk, var validationErrors) = _sadConsoleHostApp.IsValidConfigWithDetails().Result;
diff --git a/src/apps/Highbyte.DotNet6502.App.SadConsole/Highbyte.DotNet6502.App.SadConsole.csproj b/src/apps/Highbyte.DotNet6502.App.SadConsole/Highbyte.DotNet6502.App.SadConsole.csproj
index 522ea780..03cb8c71 100644
--- a/src/apps/Highbyte.DotNet6502.App.SadConsole/Highbyte.DotNet6502.App.SadConsole.csproj
+++ b/src/apps/Highbyte.DotNet6502.App.SadConsole/Highbyte.DotNet6502.App.SadConsole.csproj
@@ -34,6 +34,7 @@
+
diff --git a/src/apps/Highbyte.DotNet6502.App.SadConsole/SadConsoleHostApp.cs b/src/apps/Highbyte.DotNet6502.App.SadConsole/SadConsoleHostApp.cs
index b3b2a9b0..6a06f579 100644
--- a/src/apps/Highbyte.DotNet6502.App.SadConsole/SadConsoleHostApp.cs
+++ b/src/apps/Highbyte.DotNet6502.App.SadConsole/SadConsoleHostApp.cs
@@ -279,7 +279,8 @@ public override void OnAfterStart(EmulatorState emulatorStateBeforeStart)
{
_monitorConsole.Init();
}
- _sadConsoleEmulatorConsole.IsFocused = true;
+
+ SetEmulatorConsoleFocus();
if (_infoConsole.IsVisible)
{
@@ -557,6 +558,12 @@ public void SetVolumePercent(float volumePercent)
_audioHandlerContext.SetMasterVolumePercent(masterVolumePercent: volumePercent);
}
+ public void SetEmulatorConsoleFocus()
+ {
+ if (_sadConsoleEmulatorConsole != null)
+ _sadConsoleEmulatorConsole.IsFocused = true;
+ }
+
private void HandleUIKeyboardInput()
{
var keyboard = GameHost.Instance.Keyboard;
diff --git a/src/apps/Highbyte.DotNet6502.App.SilkNetNative/Highbyte.DotNet6502.App.SilkNetNative.csproj b/src/apps/Highbyte.DotNet6502.App.SilkNetNative/Highbyte.DotNet6502.App.SilkNetNative.csproj
index 020c99f1..67b95b59 100644
--- a/src/apps/Highbyte.DotNet6502.App.SilkNetNative/Highbyte.DotNet6502.App.SilkNetNative.csproj
+++ b/src/apps/Highbyte.DotNet6502.App.SilkNetNative/Highbyte.DotNet6502.App.SilkNetNative.csproj
@@ -34,6 +34,7 @@
+
diff --git a/src/apps/Highbyte.DotNet6502.App.SilkNetNative/SilkNetImGuiMenu.cs b/src/apps/Highbyte.DotNet6502.App.SilkNetNative/SilkNetImGuiMenu.cs
index 6dc2a573..e26e8fc1 100644
--- a/src/apps/Highbyte.DotNet6502.App.SilkNetNative/SilkNetImGuiMenu.cs
+++ b/src/apps/Highbyte.DotNet6502.App.SilkNetNative/SilkNetImGuiMenu.cs
@@ -8,6 +8,7 @@
using Highbyte.DotNet6502.Utils;
using Microsoft.Extensions.Logging;
using NativeFileDialogSharp;
+using TextCopy;
namespace Highbyte.DotNet6502.App.SilkNetNative;
@@ -443,6 +444,19 @@ private void DrawC64Config()
}
ImGui.EndDisabled();
+ // C64 paste text
+ ImGui.BeginDisabled(disabled: EmulatorState == EmulatorState.Uninitialized);
+ if (ImGui.Button("Paste"))
+ {
+ var c64 = (C64)_silkNetHostApp.CurrentRunningSystem!;
+ var text = ClipboardService.GetText();
+ if (string.IsNullOrEmpty(text))
+ return;
+ c64.TextPaste.Paste(text);
+
+ }
+ ImGui.EndDisabled();
+
// C64 config
ImGui.BeginDisabled(disabled: !(EmulatorState == EmulatorState.Uninitialized));
if (_c64ConfigUI == null)
diff --git a/src/apps/Highbyte.DotNet6502.App.WASM/Highbyte.DotNet6502.App.WASM.csproj b/src/apps/Highbyte.DotNet6502.App.WASM/Highbyte.DotNet6502.App.WASM.csproj
index 1d8bb3ad..dd8aff23 100644
--- a/src/apps/Highbyte.DotNet6502.App.WASM/Highbyte.DotNet6502.App.WASM.csproj
+++ b/src/apps/Highbyte.DotNet6502.App.WASM/Highbyte.DotNet6502.App.WASM.csproj
@@ -37,6 +37,7 @@
+
diff --git a/src/apps/Highbyte.DotNet6502.App.WASM/Pages/Commodore64/C64Menu.razor b/src/apps/Highbyte.DotNet6502.App.WASM/Pages/Commodore64/C64Menu.razor
index 6402d613..87bb49f7 100644
--- a/src/apps/Highbyte.DotNet6502.App.WASM/Pages/Commodore64/C64Menu.razor
+++ b/src/apps/Highbyte.DotNet6502.App.WASM/Pages/Commodore64/C64Menu.razor
@@ -5,6 +5,7 @@
@using static Highbyte.DotNet6502.App.WASM.Pages.Index;
@using Highbyte.DotNet6502.App.WASM.Emulator.SystemSetup;
@using Highbyte.DotNet6502.Utils;
+@using TextCopy
@if(Parent.Initialized && Parent.WasmHost.SelectedSystemName == SYSTEM_NAME)
{
@@ -54,6 +55,10 @@
+
+
Misc.
+
+
@@ -93,6 +98,7 @@
@inject IJSRuntime Js
@inject HttpClient HttpClient
@inject ILoggerFactory LoggerFactory
+ @inject IClipboard Clipboard
[Parameter]
public Highbyte.DotNet6502.App.WASM.Pages.Index Parent { get; set; } = default!;
@@ -208,6 +214,8 @@
protected bool OnBasicFilePickerDisabled => Parent.CurrentEmulatorState == EmulatorState.Uninitialized;
+ protected bool OnPasteTextDisabled => Parent.CurrentEmulatorState == EmulatorState.Uninitialized;
+
///
/// Open Load binary file dialog
///
@@ -447,14 +455,9 @@
c64.InitBasicMemoryVariables(loadedAtAddress, fileLength);
}
- // Send "list" + Enter to the keyboard buffer to immediately list the loaded program
- var c64Keyboard = c64.Cia.Keyboard;
- // Bypass keyboard matrix scanning and send directly to keyboard buffer?
- c64Keyboard.InsertPetsciiCharIntoBuffer(Petscii.CharToPetscii['l']);
- c64Keyboard.InsertPetsciiCharIntoBuffer(Petscii.CharToPetscii['i']);
- c64Keyboard.InsertPetsciiCharIntoBuffer(Petscii.CharToPetscii['s']);
- c64Keyboard.InsertPetsciiCharIntoBuffer(Petscii.CharToPetscii['t']);
- c64Keyboard.InsertPetsciiCharIntoBuffer(Petscii.CharToPetscii[(char)13]);
+ // Send "list" + NewLine (Return) to the keyboard buffer to immediately list the loaded program.
+ // Bypass keyboard matrix scanning and send directly to keyboard buffer.
+ c64.TextPaste.Paste("list\n");
}
catch (Exception ex)
@@ -465,4 +468,16 @@
await Parent.OnStart(new());
}
+
+
+ private async Task PasteText()
+ {
+ var c64 = (C64)Parent.WasmHost.CurrentRunningSystem!;
+ var text = await Clipboard.GetTextAsync();
+ if (string.IsNullOrEmpty(text))
+ return;
+ c64.TextPaste.Paste(text);
+
+ await Parent.FocusEmulator();
+ }
}
\ No newline at end of file
diff --git a/src/apps/Highbyte.DotNet6502.App.WASM/Pages/Index.razor.cs b/src/apps/Highbyte.DotNet6502.App.WASM/Pages/Index.razor.cs
index 29bc78a6..4b72e223 100644
--- a/src/apps/Highbyte.DotNet6502.App.WASM/Pages/Index.razor.cs
+++ b/src/apps/Highbyte.DotNet6502.App.WASM/Pages/Index.razor.cs
@@ -640,7 +640,7 @@ private void OnKeyUpMonitor(KeyboardEventArgs e)
_wasmHost.Monitor.OnKeyUp(e);
}
- private async Task FocusEmulator()
+ public async Task FocusEmulator()
{
await Js!.InvokeVoidAsync("focusId", "emulatorSKGLView", 100); // Hack: Delay of x ms for focus to work.
}
diff --git a/src/apps/Highbyte.DotNet6502.App.WASM/Program.cs b/src/apps/Highbyte.DotNet6502.App.WASM/Program.cs
index 4c4ebcae..0980a55e 100644
--- a/src/apps/Highbyte.DotNet6502.App.WASM/Program.cs
+++ b/src/apps/Highbyte.DotNet6502.App.WASM/Program.cs
@@ -4,6 +4,7 @@
using Blazored.LocalStorage;
using Toolbelt.Blazor.Extensions.DependencyInjection;
using Highbyte.DotNet6502.Systems.Logging.Console;
+using TextCopy;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add
("#app");
@@ -12,6 +13,7 @@
builder.Services.AddBlazoredModal();
builder.Services.AddBlazoredLocalStorage();
builder.Services.AddGamepadList();
+builder.Services.InjectClipboard();
builder.Logging.ClearProviders();
builder.Logging.AddDotNet6502Console();
diff --git a/src/libraries/Highbyte.DotNet6502.Systems.Commodore64/C64.cs b/src/libraries/Highbyte.DotNet6502.Systems.Commodore64/C64.cs
index 947dae64..597550d5 100644
--- a/src/libraries/Highbyte.DotNet6502.Systems.Commodore64/C64.cs
+++ b/src/libraries/Highbyte.DotNet6502.Systems.Commodore64/C64.cs
@@ -9,6 +9,7 @@
using Highbyte.DotNet6502.Systems.Instrumentation;
using Highbyte.DotNet6502.Systems.Instrumentation.Stats;
using Highbyte.DotNet6502.Utils;
+using Highbyte.DotNet6502.Systems.Commodore64.Utils;
namespace Highbyte.DotNet6502.Systems.Commodore64;
@@ -53,9 +54,9 @@ public class C64 : ISystem, ISystemMonitor
private readonly ElapsedMillisecondsTimedStatSystem _postInstructionAudioCallbackStat;
private readonly ElapsedMillisecondsTimedStatSystem _postInstructionVideoCallbackStat;
-
public bool RememberVic2RegistersPerRasterLine { get; set; } = true;
+ public C64TextPaste TextPaste { get; private set; }
//public static ROM[] ROMS = new ROM[]
//{
@@ -100,6 +101,9 @@ public ExecEvaluatorTriggerResult ExecuteOneFrame(
_postInstructionAudioCallbackStat.Stop(); // Stop stat (was continiously updated after each instruction)
_postInstructionVideoCallbackStat.Stop(); // Stop stat (was continiously updated after each instruction)
+ // Check if any text should be pasted to the keyboard buffer (pasted text set by host system, and each character insterted to the C64 keyboard buffer one character per frame)
+ TextPaste.InsertNextCharacterToKeyboardBuffer();
+
// Update sprite collision state
_spriteCollisionStat.Start();
Vic2.SpriteManager.SetCollitionDetectionStatesAndIRQ();
@@ -160,7 +164,7 @@ public ExecEvaluatorTriggerResult ExecuteOneInstruction(
return ExecEvaluatorTriggerResult.NotTriggered;
}
- private C64(ILogger logger)
+ private C64(ILogger logger, ILoggerFactory loggerFactory)
{
_logger = logger;
_spriteCollisionStat = Instrumentations.Add($"{StatsCategory}-SpriteCollision", new ElapsedMillisecondsTimedStatSystem(this));
@@ -193,8 +197,8 @@ public static C64 BuildC64(C64Config c64Config, ILoggerFactory loggerFactory)
var vic2Model = c64Model.Vic2Models.Single(x => x.Name == c64Config.Vic2Model);
- var logger = loggerFactory.CreateLogger(typeof(C64).Name);
- var c64 = new C64(logger)
+ var logger = loggerFactory.CreateLogger();
+ var c64 = new C64(logger, loggerFactory)
{
Model = c64Model,
RAM = ram,
@@ -220,6 +224,8 @@ public static C64 BuildC64(C64Config c64Config, ILoggerFactory loggerFactory)
var mem = c64.CreateC64Memory(ram, io, romData);
c64.Mem = mem;
+ c64.TextPaste = new C64TextPaste(c64, loggerFactory);
+
// Configure the current memory configuration on startup
SetStartupBank(c64);
diff --git a/src/libraries/Highbyte.DotNet6502.Systems.Commodore64/TimerAndPeripheral/C64Keyboard.cs b/src/libraries/Highbyte.DotNet6502.Systems.Commodore64/TimerAndPeripheral/C64Keyboard.cs
index 9a3647b9..d04cc328 100644
--- a/src/libraries/Highbyte.DotNet6502.Systems.Commodore64/TimerAndPeripheral/C64Keyboard.cs
+++ b/src/libraries/Highbyte.DotNet6502.Systems.Commodore64/TimerAndPeripheral/C64Keyboard.cs
@@ -148,17 +148,21 @@ public byte GetPressedKeysForSelectedMatrixRow()
///
/// Inserts a PETSCII character directly into the keyboard buffer, bypassing keyboard matrix.
/// Can be useful when wanting to type Basic commands on behalf of the user.
+ ///
+ /// Returns true if the character was inserted into the buffer, false if the buffer is full (and character couldn't be inserted).
///
///
- public void InsertPetsciiCharIntoBuffer(byte petsciiChar)
+ ///
+ public bool InsertPetsciiCharIntoBuffer(byte petsciiChar)
{
// Address: 0x00c6: Keyboard buffer index
// Address: 0x0277 - 0x0280: Keyboard buffer
var bufferIndex = _c64.Mem[0x00c6];
if (bufferIndex >= 10)
- return;
+ return false;
_c64.Mem[0x00c6]++;
_c64.Mem[(ushort)(0x0277 + bufferIndex)] = petsciiChar;
+ return true;
}
///
diff --git a/src/libraries/Highbyte.DotNet6502.Systems.Commodore64/Utils/C64TextPaste.cs b/src/libraries/Highbyte.DotNet6502.Systems.Commodore64/Utils/C64TextPaste.cs
new file mode 100644
index 00000000..4507782b
--- /dev/null
+++ b/src/libraries/Highbyte.DotNet6502.Systems.Commodore64/Utils/C64TextPaste.cs
@@ -0,0 +1,64 @@
+using Highbyte.DotNet6502.Systems.Commodore64.Video;
+using Microsoft.Extensions.Logging;
+
+namespace Highbyte.DotNet6502.Systems.Commodore64.Utils;
+public class C64TextPaste
+{
+ private readonly Queue _charQueue = new();
+ private readonly ILogger _logger;
+ private readonly C64 _c64;
+
+ internal bool HasCharactersPending => _charQueue.Count > 0;
+
+
+ public C64TextPaste(C64 c64, ILoggerFactory loggerFactory)
+ {
+ _logger = loggerFactory.CreateLogger();
+ _c64 = c64;
+ }
+
+ public void Paste(string text)
+ {
+ foreach (char c in text)
+ _charQueue.Enqueue(c);
+ }
+
+ internal void InsertNextCharacterToKeyboardBuffer()
+ {
+ bool foundChar = _charQueue.TryPeek(out char ansiChar);
+ if (!foundChar)
+ return;
+
+ // In Windows, a new line is CRLF (Carrige Return 13 and Line Feed 10)
+ // In Linux and macOS, a new line is only LF (Line feed 10).
+ // C64 only uses LF (13) which is "Return" for new line.
+ //
+ // Ignore Windows LF (10), and map Line Feed for all systems (10) to C64 Return (13).
+ if (ansiChar == 13)
+ {
+ _charQueue.Dequeue();
+ return;
+ }
+
+ if (ansiChar == 10)
+ ansiChar = (char)13;
+
+ if (!Petscii.CharToPetscii.ContainsKey(ansiChar))
+ {
+ _charQueue.Dequeue();
+ _logger.LogWarning($"'{ansiChar}' has no mapped PetscII char.");
+ return;
+ }
+
+ var petsciiChar = Petscii.CharToPetscii[ansiChar];
+ var inserted = _c64.Cia.Keyboard.InsertPetsciiCharIntoBuffer(petsciiChar);
+ if (inserted)
+ {
+ _charQueue.Dequeue();
+ }
+ else
+ {
+ _logger.LogWarning($"'{ansiChar}' could not be inserted into keyboard buffer.");
+ }
+ }
+}