1
0
mirror of https://github.com/livingcomputermuseum/ContrAlto.git synced 2026-04-07 22:10:34 +00:00

Added support for Unix/OS X using Mono. This uses SDL2 for display, keyboard, and mouse.

This commit is contained in:
Josh Dersch
2017-06-07 11:18:32 -07:00
parent 4bc85daa36
commit 6cadb08b0e
27 changed files with 3123 additions and 33887 deletions

View File

@@ -8,9 +8,6 @@ EndProject
Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "ContraltoSetup", "ContraltoSetup\ContraltoSetup.wixproj", "{47BBC195-80C5-43F3-B691-7D27B0803B84}"
EndProject
Global
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
@@ -30,6 +27,7 @@ Global
{CC6D96B3-8099-4497-8AD8-B0795A3353EA}.Release|Any CPU.Build.0 = Release|Any CPU
{CC6D96B3-8099-4497-8AD8-B0795A3353EA}.Release|x64.ActiveCfg = Release|x64
{CC6D96B3-8099-4497-8AD8-B0795A3353EA}.Release|x86.ActiveCfg = Release|x86
{CC6D96B3-8099-4497-8AD8-B0795A3353EA}.Release|x86.Build.0 = Release|x86
{47BBC195-80C5-43F3-B691-7D27B0803B84}.Debug|Any CPU.ActiveCfg = Debug|x86
{47BBC195-80C5-43F3-B691-7D27B0803B84}.Debug|x64.ActiveCfg = Debug|x86
{47BBC195-80C5-43F3-B691-7D27B0803B84}.Debug|x86.ActiveCfg = Debug|x86
@@ -45,4 +43,7 @@ Global
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
EndGlobalSection
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
EndGlobalSection
EndGlobal

View File

@@ -178,18 +178,18 @@ namespace Contralto.CPU
private static RomFile[] _constantRomsAltoI =
{
new RomFile(Configuration.GetAltoIRomPath("c0_23.BIN"), 0x000, 12),
new RomFile(Configuration.GetAltoIRomPath("c1_23.BIN"), 0x000, 8),
new RomFile(Configuration.GetAltoIRomPath("c2_23.BIN"), 0x000, 4),
new RomFile(Configuration.GetAltoIRomPath("c3_23.BIN"), 0x000, 0),
new RomFile(Configuration.GetAltoIRomPath("C0_23.BIN"), 0x000, 12),
new RomFile(Configuration.GetAltoIRomPath("C1_23.BIN"), 0x000, 8),
new RomFile(Configuration.GetAltoIRomPath("C2_23.BIN"), 0x000, 4),
new RomFile(Configuration.GetAltoIRomPath("C3_23.BIN"), 0x000, 0),
};
private static RomFile[] _constantRomsAltoII =
{
new RomFile(Configuration.GetAltoIIRomPath("c0"), 0x000, 12),
new RomFile(Configuration.GetAltoIIRomPath("c1"), 0x000, 8),
new RomFile(Configuration.GetAltoIIRomPath("c2"), 0x000, 4),
new RomFile(Configuration.GetAltoIIRomPath("c3"), 0x000, 0),
new RomFile(Configuration.GetAltoIIRomPath("C0"), 0x000, 12),
new RomFile(Configuration.GetAltoIIRomPath("C1"), 0x000, 8),
new RomFile(Configuration.GetAltoIIRomPath("C2"), 0x000, 4),
new RomFile(Configuration.GetAltoIIRomPath("C3"), 0x000, 0),
};
private static RomFile _acSourceRoms = new RomFile(Configuration.GetRomPath("ACSOURCE.NEW"), 0x000, 0);

View File

@@ -447,24 +447,24 @@ namespace Contralto.CPU
private static RomFile[] _uCodeRomsAltoII =
{
// first K (standard uCode)
new RomFile(Configuration.GetAltoIIRomPath("u55"), 0x000, 28),
new RomFile(Configuration.GetAltoIIRomPath("u64"), 0x000, 24),
new RomFile(Configuration.GetAltoIIRomPath("u65"), 0x000, 20),
new RomFile(Configuration.GetAltoIIRomPath("u63"), 0x000, 16),
new RomFile(Configuration.GetAltoIIRomPath("u53"), 0x000, 12),
new RomFile(Configuration.GetAltoIIRomPath("u60"), 0x000, 8),
new RomFile(Configuration.GetAltoIIRomPath("u61"), 0x000, 4),
new RomFile(Configuration.GetAltoIIRomPath("u62"), 0x000, 0),
new RomFile(Configuration.GetAltoIIRomPath("U55"), 0x000, 28),
new RomFile(Configuration.GetAltoIIRomPath("U64"), 0x000, 24),
new RomFile(Configuration.GetAltoIIRomPath("U65"), 0x000, 20),
new RomFile(Configuration.GetAltoIIRomPath("U63"), 0x000, 16),
new RomFile(Configuration.GetAltoIIRomPath("U53"), 0x000, 12),
new RomFile(Configuration.GetAltoIIRomPath("U60"), 0x000, 8),
new RomFile(Configuration.GetAltoIIRomPath("U61"), 0x000, 4),
new RomFile(Configuration.GetAltoIIRomPath("U62"), 0x000, 0),
// second K (MESA 5.0)
new RomFile(Configuration.GetAltoIIRomPath("u54"), 0x400, 28),
new RomFile(Configuration.GetAltoIIRomPath("u74"), 0x400, 24),
new RomFile(Configuration.GetAltoIIRomPath("u75"), 0x400, 20),
new RomFile(Configuration.GetAltoIIRomPath("u73"), 0x400, 16),
new RomFile(Configuration.GetAltoIIRomPath("u52"), 0x400, 12),
new RomFile(Configuration.GetAltoIIRomPath("u70"), 0x400, 8),
new RomFile(Configuration.GetAltoIIRomPath("u71"), 0x400, 4),
new RomFile(Configuration.GetAltoIIRomPath("u72"), 0x400, 0)
new RomFile(Configuration.GetAltoIIRomPath("U54"), 0x400, 28),
new RomFile(Configuration.GetAltoIIRomPath("U74"), 0x400, 24),
new RomFile(Configuration.GetAltoIIRomPath("U75"), 0x400, 20),
new RomFile(Configuration.GetAltoIIRomPath("U73"), 0x400, 16),
new RomFile(Configuration.GetAltoIIRomPath("U52"), 0x400, 12),
new RomFile(Configuration.GetAltoIIRomPath("U70"), 0x400, 8),
new RomFile(Configuration.GetAltoIIRomPath("U71"), 0x400, 4),
new RomFile(Configuration.GetAltoIIRomPath("U72"), 0x400, 0)
};

View File

@@ -18,6 +18,7 @@
using Contralto.Logging;
using System;
using System.IO;
using System.Reflection;
namespace Contralto
{
@@ -86,13 +87,13 @@ namespace Contralto
{
static Configuration()
{
// Initialize things to defaults.
// Initialize things to defaults.
HostAddress = 0x22;
BootAddress = 0;
BootFile = 0;
SystemType = SystemType.TwoKRom;
SystemType = SystemType.OneKRom;
InterlaceDisplay = false;
@@ -202,7 +203,7 @@ namespace Contralto
public static string GetAltoIRomPath(string romFileName)
{
return Path.Combine("ROM", "AltoI", romFileName);
return Path.Combine("ROM", "AltoI", romFileName);
}
public static string GetAltoIIRomPath(string romFileName)
@@ -216,26 +217,29 @@ namespace Contralto
}
/// <summary>
/// Reads the current configuration file from the app's configuration.
///
/// TODO: use reflection to do this.
/// Reads the current configuration file from the appropriate place.
/// </summary>
public static void ReadConfiguration()
{
Drive0Image = (string)Properties.Settings.Default["Drive0Image"];
Drive1Image = (string)Properties.Settings.Default["Drive1Image"];
SystemType = (SystemType)Properties.Settings.Default["SystemType"];
HostAddress = (byte)Properties.Settings.Default["HostAddress"];
HostPacketInterfaceName = (string)Properties.Settings.Default["HostPacketInterfaceName"];
HostPacketInterfaceType = (PacketInterfaceType)Properties.Settings.Default["HostPacketInterfaceType"];
AlternateBootType = (AlternateBootType)Properties.Settings.Default["AlternateBootType"];
BootAddress = (ushort)Properties.Settings.Default["BootAddress"];
BootFile = (ushort)Properties.Settings.Default["BootFile"];
InterlaceDisplay = (bool)Properties.Settings.Default["InterlaceDisplay"];
ThrottleSpeed = (bool)Properties.Settings.Default["ThrottleSpeed"];
EnableAudioDAC = (bool)Properties.Settings.Default["EnableAudioDAC"];
EnableAudioDACCapture = (bool)Properties.Settings.Default["EnableAudioDACCapture"];
AudioDACCapturePath = (string)Properties.Settings.Default["AudioDACCapturePath"];
{
if (Configuration.Platform == PlatformType.Windows
&& Program.StartupArgs.Length == 0)
{
//
// By default, on Windows we use the app Settings functionality
// to store settings in the registry on a per-user basis.
// If a configuration file is specified, we will use it instead.
//
ReadConfigurationWindows();
}
else
{
//
// On UNIX platforms we read in a configuration file.
// This is mostly because Mono's support for Properties.Settings
// is broken in inexplicable ways and I'm tired of fighting with it.
//
ReadConfigurationUnix();
}
}
/// <summary>
@@ -243,23 +247,221 @@ namespace Contralto
/// </summary>
public static void WriteConfiguration()
{
Properties.Settings.Default["Drive0Image"] = Drive0Image;
Properties.Settings.Default["Drive1Image"] = Drive1Image;
Properties.Settings.Default["SystemType"] = (int)SystemType;
Properties.Settings.Default["HostAddress"] = HostAddress;
Properties.Settings.Default["HostPacketInterfaceName"] = HostPacketInterfaceName;
Properties.Settings.Default["HostPacketInterfaceType"] = (int)HostPacketInterfaceType;
Properties.Settings.Default["AlternateBootType"] = (int)AlternateBootType;
Properties.Settings.Default["BootAddress"] = BootAddress;
Properties.Settings.Default["BootFile"] = BootFile;
Properties.Settings.Default["InterlaceDisplay"] = InterlaceDisplay;
Properties.Settings.Default["ThrottleSpeed"] = ThrottleSpeed;
Properties.Settings.Default["EnableAudioDAC"] = EnableAudioDAC;
Properties.Settings.Default["EnableAudioDACCapture"] = EnableAudioDACCapture;
Properties.Settings.Default["AudioDACCapturePath"] = AudioDACCapturePath;
if (Configuration.Platform == PlatformType.Windows)
{
WriteConfigurationWindows();
}
else
{
//
// At the moment the configuration files are read-only
// on UNIX platforms.
//
}
}
private static void ReadConfigurationWindows()
{
Properties.Settings.Default.AudioDACCapturePath = Properties.Settings.Default.AudioDACCapturePath;
Drive0Image = Properties.Settings.Default.Drive0Image;
Drive1Image = Properties.Settings.Default.Drive1Image;
SystemType = (SystemType)Properties.Settings.Default.SystemType;
HostAddress = Properties.Settings.Default.HostAddress;
HostPacketInterfaceName = Properties.Settings.Default.HostPacketInterfaceName;
HostPacketInterfaceType = (PacketInterfaceType)Properties.Settings.Default.HostPacketInterfaceType;
AlternateBootType = (AlternateBootType)Properties.Settings.Default.AlternateBootType;
BootAddress = Properties.Settings.Default.BootAddress;
BootFile = Properties.Settings.Default.BootFile;
InterlaceDisplay = Properties.Settings.Default.InterlaceDisplay;
ThrottleSpeed = Properties.Settings.Default.ThrottleSpeed;
EnableAudioDAC = Properties.Settings.Default.EnableAudioDAC;
EnableAudioDACCapture = Properties.Settings.Default.EnableAudioDACCapture;
AudioDACCapturePath = Properties.Settings.Default.AudioDACCapturePath;
}
private static void WriteConfigurationWindows()
{
Properties.Settings.Default.Drive0Image = Drive0Image;
Properties.Settings.Default.Drive1Image = Drive1Image;
Properties.Settings.Default.SystemType = (int)SystemType;
Properties.Settings.Default.HostAddress = HostAddress;
Properties.Settings.Default.HostPacketInterfaceName = HostPacketInterfaceName;
Properties.Settings.Default.HostPacketInterfaceType = (int)HostPacketInterfaceType;
Properties.Settings.Default.AlternateBootType = (int)AlternateBootType;
Properties.Settings.Default.BootAddress = BootAddress;
Properties.Settings.Default.BootFile = BootFile;
Properties.Settings.Default.InterlaceDisplay = InterlaceDisplay;
Properties.Settings.Default.ThrottleSpeed = ThrottleSpeed;
Properties.Settings.Default.EnableAudioDAC = EnableAudioDAC;
Properties.Settings.Default.EnableAudioDACCapture = EnableAudioDACCapture;
Properties.Settings.Default.AudioDACCapturePath = AudioDACCapturePath;
Properties.Settings.Default.AudioDACCapturePath = Properties.Settings.Default.AudioDACCapturePath;
Properties.Settings.Default.Save();
}
private static void ReadConfigurationUnix()
{
string configFilePath = null;
if (Program.StartupArgs.Length > 0)
{
configFilePath = Program.StartupArgs[0];
}
else
{
// No config file specified, default.
configFilePath = "Contralto.cfg";
}
//
// Check that the configuration file exists.
// If not, we will warn the user and use default settings.
//
if (!File.Exists(configFilePath))
{
Console.WriteLine("Configuration file {0} does not exist or cannot be accessed. Using default settings.", configFilePath);
return;
}
using (StreamReader configStream = new StreamReader(configFilePath))
{
//
// Config file consists of text lines containing name / value pairs:
// <Name>=<Value>
// Whitespace is ignored.
//
int lineNumber = 0;
while (!configStream.EndOfStream)
{
lineNumber++;
string line = configStream.ReadLine().Trim();
if (string.IsNullOrEmpty(line))
{
// Empty line, ignore.
continue;
}
if (line.StartsWith("#"))
{
// Comment to EOL, ignore.
continue;
}
// Find the '=' separating tokens and ensure there are just two.
string[] tokens = line.Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
if (tokens.Length < 2)
{
Console.WriteLine(
"{0} line {1}: Invalid syntax.", configFilePath, lineNumber);
continue;
}
string parameter = tokens[0].Trim();
string value = tokens[1].Trim();
// Reflect over the public, static properties in this class and see if the parameter matches one of them
// If not, it's an error, if it is then we attempt to coerce the value to the correct type.
System.Reflection.FieldInfo[] info = typeof(Configuration).GetFields(BindingFlags.Public | BindingFlags.Static);
bool bMatch = false;
foreach (FieldInfo field in info)
{
// Case-insensitive compare.
if (field.Name.ToLowerInvariant() == parameter.ToLowerInvariant())
{
bMatch = true;
//
// Switch on the type of the field and attempt to convert the value to the appropriate type.
// At this time we support only strings and integers.
//
try
{
switch (field.FieldType.Name)
{
case "Int32":
{
int v = Convert.ToInt32(value, 8);
field.SetValue(null, v);
}
break;
case "UInt16":
{
UInt16 v = Convert.ToUInt16(value, 8);
field.SetValue(null, v);
}
break;
case "Byte":
{
byte v = Convert.ToByte(value, 8);
field.SetValue(null, v);
}
break;
case "String":
{
field.SetValue(null, value);
}
break;
case "Boolean":
{
bool v = bool.Parse(value);
field.SetValue(null, v);
}
break;
case "SystemType":
{
field.SetValue(null, Enum.Parse(typeof(SystemType), value, true));
}
break;
case "PacketInterfaceType":
{
field.SetValue(null, Enum.Parse(typeof(PacketInterfaceType), value, true));
}
break;
case "AlternateBootType":
{
field.SetValue(null, Enum.Parse(typeof(AlternateBootType), value, true));
}
break;
case "LogType":
{
field.SetValue(null, Enum.Parse(typeof(LogType), value, true));
}
break;
case "LogComponent":
{
field.SetValue(null, Enum.Parse(typeof(LogComponent), value, true));
}
break;
}
}
catch
{
Console.WriteLine(
"{0} line {1}: Value '{2}' is invalid for parameter '{3}'.", configFilePath, lineNumber, value, parameter);
}
}
}
if (!bMatch)
{
Console.WriteLine(
"{0} line {1}: Unknown configuration parameter '{2}'.", configFilePath, lineNumber, parameter);
}
}
}
}
}
}

20
Contralto/Contralto.cfg Normal file
View File

@@ -0,0 +1,20 @@
# contralto.cfg:
#
# This file contains configuration parameters for ContrAlto.
# All integers are specified in octal.
#
# System configuration
SystemType = TwoKRom
HostAddress = 42
# Host networking configuration
HostPacketInterfaceType = None
HostPacketInterfaceName = None
# Emulation Options
ThrottleSpeed = True
InterlaceDisplay = False
BootAddress = 0
BootFile = 0
AlternateBootType = Ethernet

View File

@@ -46,6 +46,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
@@ -76,6 +77,7 @@
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
@@ -86,6 +88,7 @@
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>Alto.ico</ApplicationIcon>
@@ -98,21 +101,28 @@
<HintPath>..\packages\NAudio.1.8.0\lib\net35\NAudio.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="PcapDotNet.Base, Version=1.0.2.21699, Culture=neutral, PublicKeyToken=4b6f3e583145a652, processorArchitecture=MSIL">
<Reference Include="PcapDotNet.Base, Version=1.0.4.25027, Culture=neutral, PublicKeyToken=06a20bc2fabb1931, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>pcap\PcapDotNet.Base.dll</HintPath>
<HintPath>..\packages\Pcap.Net.x86.1.0.4.1\lib\net45\PcapDotNet.Base.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="PcapDotNet.Core, Version=1.0.2.21711, Culture=neutral, PublicKeyToken=4b6f3e583145a652, processorArchitecture=x86">
<Reference Include="PcapDotNet.Core, Version=1.0.4.25067, Culture=neutral, PublicKeyToken=06a20bc2fabb1931, processorArchitecture=x86">
<SpecificVersion>False</SpecificVersion>
<HintPath>pcap\PcapDotNet.Core.dll</HintPath>
<HintPath>..\packages\Pcap.Net.x86.1.0.4.1\lib\net45\PcapDotNet.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="PcapDotNet.Core.Extensions, Version=1.0.2.21712, Culture=neutral, PublicKeyToken=4b6f3e583145a652, processorArchitecture=x86">
<Reference Include="PcapDotNet.Core.Extensions, Version=1.0.4.25069, Culture=neutral, PublicKeyToken=06a20bc2fabb1931, processorArchitecture=x86">
<SpecificVersion>False</SpecificVersion>
<HintPath>pcap\PcapDotNet.Core.Extensions.dll</HintPath>
<HintPath>..\packages\Pcap.Net.x86.1.0.4.1\lib\net45\PcapDotNet.Core.Extensions.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="PcapDotNet.Packets, Version=1.0.2.21701, Culture=neutral, PublicKeyToken=4b6f3e583145a652, processorArchitecture=MSIL">
<Reference Include="PcapDotNet.Packets, Version=1.0.4.25028, Culture=neutral, PublicKeyToken=06a20bc2fabb1931, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>pcap\PcapDotNet.Packets.dll</HintPath>
<HintPath>..\packages\Pcap.Net.x86.1.0.4.1\lib\net45\PcapDotNet.Packets.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="SDL2-CS, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\SDL2-CS.dll.2.0.0.0\lib\net20\SDL2-CS.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
@@ -143,6 +153,11 @@
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
<Compile Include="SdlUI\SdlAltoWindow.cs" />
<Compile Include="SdlUI\ConsoleExecutor.cs" />
<Compile Include="SdlUI\DebuggerAttributes.cs" />
<Compile Include="SdlUI\DebuggerPrompt.cs" />
<Compile Include="SdlUI\SdlConsole.cs" />
<Compile Include="UI\AboutBox.cs">
<SubType>Form</SubType>
</Compile>
@@ -215,6 +230,9 @@
</ItemGroup>
<ItemGroup>
<None Include="app.manifest" />
<None Include="Contralto.cfg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Disassembly\altocode24.mu" />
<None Include="App.config">
<SubType>Designer</SubType>
@@ -487,6 +505,9 @@
<None Include="Resources\DiskNoAccess.bmp" />
<None Include="Resources\dragon.png" />
<EmbeddedResource Include="dragon.png" />
<Content Include="readme-mono.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="readme.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

View File

@@ -29,6 +29,12 @@ namespace Contralto.Display
/// </summary>
public interface IAltoDisplay
{
/// <summary>
/// Attach the AltoSystem to this display instance.
/// </summary>
/// <param name="system"></param>
void AttachSystem(AltoSystem system);
/// <summary>
/// Renders a word's worth of data to the specified scanline and word offset.
/// </summary>

View File

@@ -82,8 +82,12 @@ namespace Contralto.IO
/// <param name="data"></param>
public void Load(int address, ushort data, TaskType task, bool extendedMemory)
{
if (Configuration.EnableAudioDAC)
{
//
// This is only supported on Windows platforms at this time.
//
if (Configuration.EnableAudioDAC &&
Configuration.Platform == PlatformType.Windows)
{
// Ensure we have a sink for audio output capture if so configured.
if (Configuration.EnableAudioDACCapture && _waveFile == null)
{

View File

@@ -16,6 +16,7 @@
*/
using Contralto.IO;
using Contralto.SdlUI;
using System;
using System.Collections.Generic;
using System.Windows.Forms;
@@ -27,11 +28,22 @@ namespace Contralto
[STAThread]
static void Main(string[] args)
{
//
// Check for command-line arguments.
// We expect at most one argument, specifying a configuration file to load.
//
StartupArgs = args;
if (args.Length > 1)
{
Console.WriteLine("usage: Contralto <config file>");
return;
}
// Handle command-line args
PrintHerald();
// See if WinPCap is installed and working
TestPCap();
TestPCap();
_system = new AltoSystem();
@@ -69,13 +81,32 @@ namespace Contralto
//
// Invoke the main UI window; this will run until the user closes it, at which
// point we are done.
//
using (AltoWindow mainWindow = new AltoWindow())
{
mainWindow.AttachSystem(_system);
Application.Run(mainWindow);
}
}
//
if (Configuration.Platform == PlatformType.Windows)
{
using (AltoWindow mainWindow = new AltoWindow())
{
mainWindow.AttachSystem(_system);
Application.Run(mainWindow);
}
}
else
{
using (SdlAltoWindow mainWindow = new SdlAltoWindow())
{
// Invoke the command-line console
SdlConsole console = new SdlConsole(_system);
console.Run(mainWindow);
// Start the SDL display running.
mainWindow.AttachSystem(_system);
mainWindow.Run();
}
}
}
public static string[] StartupArgs;
private static void OnProcessExit(object sender, EventArgs e)
{
@@ -86,6 +117,8 @@ namespace Contralto
//
_system.CommitDiskPack(0);
_system.CommitDiskPack(1);
_system.Shutdown();
//
// Commit current configuration to disk
@@ -96,25 +129,32 @@ namespace Contralto
private static void PrintHerald()
{
Console.WriteLine("ContrAlto v1.1 (c) 2015, 2016 Living Computers: Museum+Labs.");
Console.WriteLine("ContrAlto v1.2 (c) 2015-2017 Living Computers: Museum+Labs.");
Console.WriteLine("Bug reports to joshd@livingcomputers.org");
Console.WriteLine();
}
private static void TestPCap()
{
// Just try enumerating interfaces, if this fails for any reason we assume
// PCap is not properly installed.
try
if (Configuration.Platform == PlatformType.Windows)
{
List<EthernetInterface> interfaces = EthernetInterface.EnumerateDevices();
Configuration.HostRawEthernetInterfacesAvailable = true;
// Just try enumerating interfaces, if this fails for any reason we assume
// PCap is not properly installed.
try
{
List<EthernetInterface> interfaces = EthernetInterface.EnumerateDevices();
Configuration.HostRawEthernetInterfacesAvailable = true;
}
catch
{
Configuration.HostRawEthernetInterfacesAvailable = false;
}
}
catch
else
{
Configuration.HostRawEthernetInterfacesAvailable = false;
Configuration.HostRawEthernetInterfacesAvailable = false;
}
}
}
private static AltoSystem _system;
}

View File

@@ -10,7 +10,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Living Computers: Museum+Labs")]
[assembly: AssemblyProduct("ContrAlto")]
[assembly: AssemblyCopyright("Copyright © Living Computers: Museum+Labs 2016")]
[assembly: AssemblyCopyright("Copyright © Living Computers: Museum+Labs 2015-2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.1.1")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyVersion("1.2.0")]
[assembly: AssemblyFileVersion("1.2.0.0")]

View File

@@ -0,0 +1,672 @@
/*
This file is part of ContrAlto.
ContrAlto is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ContrAlto is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with ContrAlto. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.IO;
namespace Contralto.SdlUI
{
/// <summary>
/// Defines a node in the debug command tree.
/// </summary>
public class DebuggerCommand
{
public DebuggerCommand(object instance, string name, String description, String usage, MethodInfo method)
{
Instance = instance;
Name = name.Trim().ToLower();
Description = description;
Usage = usage;
Methods = new List<MethodInfo>(4);
if (method != null)
{
Methods.Add(method);
}
SubCommands = new List<DebuggerCommand>();
}
public object Instance;
public string Name;
public string Description;
public string Usage;
public List<MethodInfo> Methods;
public List<DebuggerCommand> SubCommands;
public override string ToString()
{
if (this.Methods.Count == 0)
{
return String.Format("{0}... ({1})", this.Name, this.SubCommands.Count);
}
else
{
return this.Name;
}
}
public void AddSubNode(List<string> words, MethodInfo method, object instance)
{
// We should never hit this case.
if (words.Count == 0)
{
throw new InvalidOperationException("Out of words building command node.");
}
// Check the root to see if a node for the first incoming word has already been added
DebuggerCommand subNode = FindSubNodeByName(words[0]);
if (subNode == null)
{
// No, it has not -- create one and add it now.
subNode = new DebuggerCommand(instance, words[0], null, null, null);
this.SubCommands.Add(subNode);
if (words.Count == 1)
{
// This is the last stop -- set the method and be done with it now.
subNode.Methods.Add(method);
// early return.
return;
}
}
else
{
// The node already exists, we will be adding a subnode, hopefully.
if (words.Count == 1)
{
//
// If we're on the last word at this point then this is an overloaded command.
// Check that we don't have any other commands with this number of arguments.
//
int argCount = method.GetParameters().Length;
foreach (MethodInfo info in subNode.Methods)
{
if (info.GetParameters().Length == argCount)
{
throw new InvalidOperationException("Duplicate overload for console command");
}
}
//
// We're ok. Add it to the method list.
//
subNode.Methods.Add(method);
// and return early.
return;
}
}
// We have more words to go.
words.RemoveAt(0);
subNode.AddSubNode(words, method, instance);
}
public DebuggerCommand FindSubNodeByName(string name)
{
DebuggerCommand found = null;
foreach (DebuggerCommand sub in SubCommands)
{
if (sub.Name == name)
{
found = sub;
break;
}
}
return found;
}
}
public class ConsoleExecutor
{
public ConsoleExecutor(params object[] commandObjects)
{
List<object> commandList = new List<object>(commandObjects);
BuildCommandTree(commandList);
_consolePrompt = new DebuggerPrompt(_commandRoot);
}
public CommandResult Prompt()
{
CommandResult next = CommandResult.Normal;
try
{
// Get the command string from the prompt.
string command = _consolePrompt.Prompt().Trim();
if (command != String.Empty)
{
next = ExecuteLine(command);
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
return next;
}
public CommandResult ExecuteScript(string scriptFile)
{
CommandResult state = CommandResult.Normal;
using (StreamReader sr = new StreamReader(scriptFile))
{
while (!sr.EndOfStream)
{
string line = sr.ReadLine();
if (!string.IsNullOrWhiteSpace(line))
{
Console.WriteLine(line);
state = ExecuteLine(line);
}
}
}
return state;
}
private CommandResult ExecuteLine(string line)
{
CommandResult next = CommandResult.Normal;
if (line.StartsWith("#"))
{
// Comments start with "#", just ignore them
}
else if(line.StartsWith("@"))
{
// A line beginning with an "@" indicates a script to execute.
string scriptFile = line.Substring(1);
next = ExecuteScript(scriptFile);
}
else
{
string[] args = null;
DebuggerCommand command = GetDebuggerCommandFromCommandString(line, out args);
if (command == null)
{
// Not a command.
Console.WriteLine("Invalid command.");
}
else
{
next = InvokeConsoleMethod(command, args);
}
}
return next;
}
private CommandResult InvokeConsoleMethod(DebuggerCommand command, string[] args)
{
MethodInfo method = null;
//
// Find the method that matches the arg count we were passed
// (i.e. handle overloaded commands).
// That this only matches on argument count is somewhat of a kluge...
//
foreach (MethodInfo m in command.Methods)
{
ParameterInfo[] paramInfo = m.GetParameters();
if (args == null && paramInfo.Length == 0 ||
paramInfo.Length == args.Length)
{
// found a match
method = m;
break;
}
}
if (method == null)
{
// invalid argument count.
throw new ArgumentException(String.Format("Invalid argument count to command."));
}
ParameterInfo[] parameterInfo = method.GetParameters();
object[] invokeParams;
if (args == null)
{
invokeParams = null;
}
else
{
invokeParams = new object[parameterInfo.Length];
}
int argIndex = 0;
for (int paramIndex = 0; paramIndex < parameterInfo.Length; paramIndex++)
{
ParameterInfo p = parameterInfo[paramIndex];
if (p.ParameterType.IsEnum)
{
//
// This is an enumeration type.
// See if we can find an enumerant that matches the argument.
//
FieldInfo[] fields = p.ParameterType.GetFields();
foreach (FieldInfo f in fields)
{
if (!f.IsSpecialName && args[argIndex].ToLower() == f.Name.ToLower())
{
invokeParams[paramIndex] = f.GetRawConstantValue();
break;
}
}
if (invokeParams[paramIndex] == null)
{
// no match, provide possible values
StringBuilder sb = new StringBuilder(String.Format("Invalid value for parameter {0}. Possible values are:", paramIndex));
foreach (FieldInfo f in fields)
{
if (!f.IsSpecialName)
{
sb.AppendFormat("{0} ", f.Name);
}
}
sb.AppendLine();
throw new ArgumentException(sb.ToString());
}
argIndex++;
}
else if (p.ParameterType.IsArray)
{
//
// If a function takes an array type, i should do something here, yeah.
//
argIndex++;
}
else
{
if (p.ParameterType == typeof(bool))
{
invokeParams[paramIndex] = bool.Parse(args[argIndex++]);
}
else if (p.ParameterType == typeof(uint))
{
invokeParams[paramIndex] = TryParseUint(args[argIndex++]);
}
else if (p.ParameterType == typeof(ushort))
{
invokeParams[paramIndex] = TryParseUshort(args[argIndex++]);
}
else if (p.ParameterType == typeof(string))
{
invokeParams[paramIndex] = args[argIndex++];
}
else if (p.ParameterType == typeof(char))
{
invokeParams[paramIndex] = (char)args[argIndex++][0];
}
else if (p.ParameterType == typeof(float))
{
invokeParams[paramIndex] = float.Parse(args[argIndex++]);
}
else
{
throw new ArgumentException(String.Format("Unhandled type for parameter {0}, type {1}", paramIndex, p.ParameterType));
}
}
}
//
// If we've made it THIS far, then we were able to parse all the commands into what they should be.
// Invoke the method on the object instance associated with the command.
//
return (CommandResult)method.Invoke(command.Instance, invokeParams);
}
enum ParseState
{
NonWhiteSpace = 0,
WhiteSpace = 1,
QuotedString = 2,
}
private List<string> SplitArgs(string commandString)
{
// We split on whitespace and specially handle quoted strings (quoted strings count as a single arg)
//
List<string> args = new List<string>();
commandString = commandString.Trim();
StringBuilder sb = new StringBuilder();
ParseState state = ParseState.NonWhiteSpace;
foreach(char c in commandString)
{
switch (state)
{
case ParseState.NonWhiteSpace:
if (char.IsWhiteSpace(c))
{
// End of token
args.Add(sb.ToString());
sb.Clear();
state = ParseState.WhiteSpace;
}
else if (c == '\"')
{
// Start of quoted string
state = ParseState.QuotedString;
}
else
{
// Character in token
sb.Append(c);
}
break;
case ParseState.WhiteSpace:
if (!char.IsWhiteSpace(c))
{
// Start of new token
if (c != '\"')
{
sb.Append(c);
state = ParseState.NonWhiteSpace;
}
else
{
// Start of quoted string
state = ParseState.QuotedString;
}
}
break;
case ParseState.QuotedString:
if (c == '\"')
{
// End of quoted string.
args.Add(sb.ToString());
sb.Clear();
state = ParseState.WhiteSpace;
}
else
{
// Character in quoted string
sb.Append(c);
}
break;
}
}
if (sb.Length > 0)
{
// Add the last token to the args list
args.Add(sb.ToString());
}
return args;
}
private DebuggerCommand GetDebuggerCommandFromCommandString(string command, out string[] args)
{
args = null;
List<string> cmdArgs = SplitArgs(command);
DebuggerCommand current = _commandRoot;
int commandIndex = 0;
while (true)
{
// If this node has an executor and no subnodes, or if this node has an executor
// and there are no further arguments, then we're done.
if ((current.Methods.Count > 0 && current.SubCommands.Count == 0) ||
(current.Methods.Count > 0 && commandIndex > cmdArgs.Count -1))
{
break;
}
if (commandIndex > cmdArgs.Count - 1)
{
// Out of args with no match.
return null;
}
// Otherwise we continue down the tree.
DebuggerCommand next = current.FindSubNodeByName(cmdArgs[commandIndex]);
commandIndex++;
if (next == null)
{
//
// If a matching subcommand was not found, then if we had a previous node with an
// executor, use that; otherwise the command is invalid.
//
if (current.Methods.Count > 0)
{
break;
}
else
{
return null;
}
}
current = next;
}
// Now current should point to the command with the executor
// and commandIndex should point to the first argument to the command.
cmdArgs.RemoveRange(0, commandIndex);
args = cmdArgs.ToArray();
return current;
}
private enum Radix
{
Binary = 2,
Octal = 8,
Decimal = 10,
Hexadecimal = 16,
}
private static uint TryParseUint(string arg)
{
uint result = 0;
Radix radix = Radix.Decimal;
switch (arg[0])
{
case 'b':
radix = Radix.Binary;
arg = arg.Remove(0, 1);
break;
case 'o':
radix = Radix.Octal;
arg = arg.Remove(0, 1);
break;
case 'd':
radix = Radix.Decimal;
arg = arg.Remove(0, 1);
break;
case 'x':
radix = Radix.Hexadecimal;
arg = arg.Remove(0, 1);
break;
default:
radix = Radix.Octal;
break;
}
try
{
result = Convert.ToUInt32(arg, (int)radix);
}
catch
{
Console.WriteLine("{0} is not a valid 32-bit value.", arg);
throw;
}
return result;
}
private static ushort TryParseUshort(string arg)
{
ushort result = 0;
Radix radix = Radix.Decimal;
switch (arg[0])
{
case 'b':
radix = Radix.Binary;
arg = arg.Remove(0, 1);
break;
case 'o':
radix = Radix.Octal;
arg = arg.Remove(0, 1);
break;
case 'd':
radix = Radix.Decimal;
arg = arg.Remove(0, 1);
break;
case 'x':
radix = Radix.Hexadecimal;
arg = arg.Remove(0, 1);
break;
default:
radix = Radix.Octal;
break;
}
try
{
result = Convert.ToUInt16(arg, (int)radix);
}
catch
{
Console.WriteLine("{0} is not a valid 16-bit value.", arg);
throw;
}
return result;
}
/// <summary>
/// Builds the debugger command tree.
/// </summary>
private void BuildCommandTree(List<object> commandObjects)
{
// Build the flat list which will be built into the tree, by walking
// the classes that provide the methods
_commandList = new List<DebuggerCommand>();
// Add ourself to the list
commandObjects.Add(this);
foreach (object commandObject in commandObjects)
{
Type type = commandObject.GetType();
foreach (MethodInfo info in type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
object[] attribs = info.GetCustomAttributes(typeof(DebuggerFunction), true);
if (attribs.Length > 1)
{
throw new InvalidOperationException(String.Format("More than one ConsoleFunction attribute set on {0}", info.Name));
}
else if (attribs.Length == 1)
{
// we have a debugger attribute set on this method
// this cast should always succeed given that we're filtering for this type above.
DebuggerFunction function = (DebuggerFunction)attribs[0];
DebuggerCommand newCommand = new DebuggerCommand(commandObject, function.CommandName, function.Description, function.Usage, info);
_commandList.Add(newCommand);
}
}
}
// Now actually build the command tree from the above list!
_commandRoot = new DebuggerCommand(null, "Root", null, null, null);
foreach (DebuggerCommand c in _commandList)
{
string[] commandWords = c.Name.Split(' ');
// This is kind of ugly, we know that at this point every command built above have only
// one method. When building the tree, overloaded commands may end up with more than one.
_commandRoot.AddSubNode(new List<string>(commandWords), c.Methods[0], c.Instance);
}
}
[DebuggerFunction("show commands", "Shows debugger commands and their descriptions.")]
private CommandResult ShowCommands()
{
foreach (DebuggerCommand cmd in _commandList)
{
if (!string.IsNullOrEmpty(cmd.Usage))
{
Console.WriteLine("{0} {2} - {1}", cmd.Name, cmd.Description, cmd.Usage);
}
else
{
Console.WriteLine("{0} - {1}", cmd.Name, cmd.Description);
}
}
return CommandResult.Normal;
}
private DebuggerPrompt _consolePrompt;
private DebuggerCommand _commandRoot;
private List<DebuggerCommand> _commandList;
}
}

View File

@@ -0,0 +1,64 @@
/*
This file is part of ContrAlto.
ContrAlto is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ContrAlto is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with ContrAlto. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
namespace Contralto.SdlUI
{
public class DebuggerFunction : Attribute
{
public DebuggerFunction(string commandName)
{
_commandName = commandName;
_usage = "<No help available>";
}
public DebuggerFunction(string commandName, string description)
{
_commandName = commandName;
_description = description;
}
public DebuggerFunction(string commandName, string description, string usage)
{
_commandName = commandName;
_description = description;
_usage = usage;
}
public string CommandName
{
get { return _commandName; }
}
public string Usage
{
get { return _usage; }
}
public string Description
{
get { return _description; }
}
private string _commandName;
private string _description;
private string _usage;
}
}

View File

@@ -0,0 +1,497 @@
/*
This file is part of ContrAlto.
ContrAlto is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ContrAlto is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with ContrAlto. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace Contralto.SdlUI
{
public class DebuggerPrompt
{
public DebuggerPrompt(DebuggerCommand root)
{
_commandTree = root;
_commandHistory = new List<string>(64);
_historyIndex = 0;
}
/// <summary>
/// Runs a nifty interactive debugger prompt.
/// </summary>
public string Prompt()
{
DisplayPrompt();
ClearInput();
UpdateOrigin();
bool entryDone = false;
while (!entryDone)
{
UpdateDisplay();
// Read one keystroke from the console...
ConsoleKeyInfo key = Console.ReadKey(true);
//Parse special chars...
switch (key.Key)
{
case ConsoleKey.Escape: //Clear input
ClearInput();
break;
case ConsoleKey.Backspace: // Delete last char
DeleteCharAtCursor(true /* backspace */);
break;
case ConsoleKey.Delete: //Delete character at cursor
DeleteCharAtCursor(false /* delete */);
break;
case ConsoleKey.LeftArrow:
MoveLeft();
break;
case ConsoleKey.RightArrow:
MoveRight();
break;
case ConsoleKey.UpArrow:
HistoryPrev();
break;
case ConsoleKey.DownArrow:
HistoryNext();
break;
case ConsoleKey.Home:
MoveToBeginning();
break;
case ConsoleKey.End:
MoveToEnd();
break;
case ConsoleKey.Tab:
DoCompletion(false /* silent */);
break;
case ConsoleKey.Enter:
DoCompletion(true /* silent */);
UpdateDisplay();
CRLF();
entryDone = true;
break;
case ConsoleKey.Spacebar:
if (!_input.EndsWith(" ") &&
DoCompletion(true /* silent */))
{
UpdateDisplay();
}
else
{
InsertChar(key.KeyChar);
}
break;
default:
// Not a special key, just insert it if it's deemed printable.
if (char.IsLetterOrDigit(key.KeyChar) ||
char.IsPunctuation(key.KeyChar) ||
char.IsSymbol(key.KeyChar) ||
char.IsWhiteSpace(key.KeyChar))
{
InsertChar(key.KeyChar);
}
break;
}
}
// Done. Add to history if input is non-empty
if (_input != string.Empty)
{
_commandHistory.Add(_input);
HistoryIndex = _commandHistory.Count - 1;
}
return _input;
}
private void DeleteCharAtCursor(bool backspace)
{
if (_input.Length == 0)
{
//nothing to delete, bail.
return;
}
if (backspace)
{
if (TextPosition == 0)
{
// We's at the beginning of the input,
// can't backspace from here.
return;
}
else
{
// remove 1 char at the position before the cursor.
// and move the cursor back one char
_input = _input.Remove(TextPosition - 1, 1);
TextPosition--;
}
}
else
{
if (TextPosition == _input.Length)
{
// At the end of input, can't delete a char from here
return;
}
else
{
// remove one char at the current cursor pos.
// do not move the cursor
_input = _input.Remove(TextPosition, 1);
}
}
}
private void DisplayPrompt()
{
Console.Write(">");
}
private void UpdateDisplay()
{
//if the current input string is shorter than the last, then we need to erase a few chars at the end.
string clear = String.Empty;
if (_input.Length < _lastInputLength)
{
StringBuilder sb = new StringBuilder(_lastInputLength - _input.Length);
for (int i = 0; i < _lastInputLength - _input.Length; i++)
{
sb.Append(' ');
}
clear = sb.ToString();
}
int column = ((_textPosition + _originColumn) % Console.BufferWidth);
int row = ((_textPosition + _originColumn) / Console.BufferWidth) + _originRow;
// Move cursor to origin to draw string
Console.CursorLeft = _originColumn;
Console.CursorTop = _originRow;
Console.Write(_input + clear);
// Move cursor to text position to draw cursor
Console.CursorLeft = column;
Console.CursorTop = row;
Console.CursorVisible = true;
_lastInputLength = _input.Length;
}
private void MoveLeft()
{
TextPosition--;
}
private void MoveRight()
{
TextPosition++;
}
private void HistoryPrev()
{
if (HistoryIndex < _commandHistory.Count)
{
_input = _commandHistory[HistoryIndex];
TextPosition = _input.Length;
HistoryIndex--;
}
}
private void HistoryNext()
{
if (HistoryIndex < _commandHistory.Count)
{
HistoryIndex++;
_input = _commandHistory[HistoryIndex];
TextPosition = _input.Length;
}
else
{
_input = String.Empty;
}
}
private void MoveToBeginning()
{
TextPosition = 0;
}
private void MoveToEnd()
{
TextPosition = _input.Length;
}
private void ClearInput()
{
_input = String.Empty;
HistoryIndex = _commandHistory.Count - 1;
TextPosition = 0;
}
private void InsertChar(char c)
{
_input = _input.Insert(TextPosition, c.ToString());
TextPosition++;
}
private void CRLF()
{
Console.WriteLine();
}
private bool DoCompletion(bool silent)
{
// This code should probably move to another class, but hey, I'm lazy.
// Take the current input and see if it matches anything in the command tree
string[] tokens = _input.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
// Save off the current cursor row; this is an ugly-ish hack to detect whether the match process
// output anything. If the cursor row changes then we'll need to move the prompt
int oldRow = Console.CursorTop;
string matchString = FuzzyMatch(_commandTree, new List<string>(tokens), silent);
bool changed = false;
if (matchString != String.Empty)
{
changed = _input.Trim().ToLower() != matchString.Trim().ToLower();
_input = matchString;
TextPosition = _input.Length;
}
if (!silent && oldRow != Console.CursorTop)
{
DisplayPrompt();
UpdateOrigin();
}
return changed;
}
private string FuzzyMatch(DebuggerCommand root, List<string> tokens, bool silent)
{
if (tokens.Count == 0)
{
if (!silent)
{
// If there are no tokens, just show the completion for the root.
PrintCompletions(root.SubCommands);
}
return String.Empty;
}
DebuggerCommand match = null;
// Search for exact matches. If we find one it's guaranteed to be unique
// so we can follow that node.
foreach (DebuggerCommand c in root.SubCommands)
{
if (c.Name.ToLower() == tokens[0].ToLower())
{
match = c;
break;
}
}
if (match == null)
{
// No exact match. Try a substring match.
// If we have an unambiguous match then we can complete it automatically.
// If the match is ambiguous, display possible completions and return String.Empty.
List<DebuggerCommand> completions = new List<DebuggerCommand>();
foreach (DebuggerCommand c in root.SubCommands)
{
if (c.Name.StartsWith(tokens[0], StringComparison.InvariantCultureIgnoreCase))
{
completions.Add(c);
}
}
if (completions.Count == 1)
{
// unambiguous match. use it.
match = completions[0];
}
else if (completions.Count > 1)
{
// ambiguous match. display possible completions.
if (!silent)
{
PrintCompletions(completions);
}
}
}
if (match == null)
{
// If we reach this point then no matches are available. return the tokens we have...
StringBuilder sb = new StringBuilder();
for (int i = 0; i < tokens.Count; i++)
{
if (i < tokens.Count - 1)
{
sb.AppendFormat("{0} ", tokens[i]);
}
else
{
sb.AppendFormat("{0}", tokens[i]);
}
}
return sb.ToString();
}
else
{
// A match was found
tokens.RemoveAt(0);
string subMatch = String.Empty;
if (tokens.Count > 0)
{
subMatch = FuzzyMatch(match, tokens, silent);
}
else // if (exactMatch)
{
if (!silent && match.SubCommands.Count > 1)
{
// More than one possible completion
// Just show the completions for this node.
PrintCompletions(match.SubCommands);
}
else if(!silent && match.SubCommands.Count == 1)
{
// Just one possible completion; fill it out.
DebuggerCommand next = match.SubCommands[0];
StringBuilder sb =
new StringBuilder(String.Format("{0} {1}", match.Name, next.Name));
while(next.SubCommands.Count > 0)
{
if (next.SubCommands.Count > 1)
{
break;
}
next = next.SubCommands[0];
sb.AppendFormat(" {0}", next.Name);
}
return sb.ToString();
}
}
if (subMatch == String.Empty)
{
return String.Format("{0} ", match.Name);
}
else
{
return String.Format("{0} {1}", match.Name, subMatch);
}
}
}
private void PrintCompletions(List<DebuggerCommand> completions)
{
// Just print all available completions at this node
Console.WriteLine();
Console.WriteLine("Possible completions are:");
foreach (DebuggerCommand c in completions)
{
Console.Write("{0}\t", c);
}
Console.WriteLine();
}
private void UpdateOrigin()
{
_originRow = Console.CursorTop;
_originColumn = Console.CursorLeft;
}
private int TextPosition
{
get
{
return _textPosition;
}
set
{
// Clip input between 0 and the length of input (+1, to allow adding text at end)
_textPosition = Math.Max(0, value);
_textPosition = Math.Min(_textPosition, _input.Length);
}
}
private int HistoryIndex
{
get
{
return _historyIndex;
}
set
{
_historyIndex = Math.Min(_commandHistory.Count - 1, value);
_historyIndex = Math.Max(0, _historyIndex);
}
}
private DebuggerCommand _commandTree;
private int _originRow;
private int _originColumn;
private string _input;
private int _textPosition;
private int _lastInputLength;
private List<string> _commandHistory;
private int _historyIndex;
}
}

View File

@@ -0,0 +1,628 @@
/*
This file is part of ContrAlto.
ContrAlto is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ContrAlto is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with ContrAlto. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using SDL2;
using Contralto.Display;
using Contralto.IO;
namespace Contralto.SdlUI
{
/// <summary>
/// AltoWindow provides a wrapper around SDL2-CS, which is in itself an incredibly thin
/// wrapper around SDL 2.0. It presents a window capable of displaying the Alto's video
/// and handles keyboard and mouse input.
///
/// This is intended for use on non-Windows platforms but will work on Windows as well.
/// </summary>
public class SdlAltoWindow : IAltoDisplay, IDisposable
{
public SdlAltoWindow()
{
InitKeymap();
}
public event EventHandler OnClosed;
public void Dispose()
{
}
public void AttachSystem(AltoSystem system)
{
_system = system;
_system.AttachDisplay(this);
}
public void Run()
{
InitSDL();
SDL.SDL_Event e;
bool running = true;
while(running)
{
//
// Run main message loop
//
while (SDL.SDL_PollEvent(out e) != 0)
{
switch(e.type)
{
case SDL.SDL_EventType.SDL_QUIT:
running = false;
OnClosed(null, null);
break;
case SDL.SDL_EventType.SDL_USEREVENT:
// This should always be the case since we only define one
// user event, but just to be truly pedantic...
if (e.user.type == _renderEventType)
{
RenderDisplay();
}
break;
case SDL.SDL_EventType.SDL_MOUSEMOTION:
MouseMove(e.motion.x, e.motion.y);
break;
case SDL.SDL_EventType.SDL_MOUSEBUTTONDOWN:
MouseDown(e.button.button, e.button.x, e.button.y);
break;
case SDL.SDL_EventType.SDL_MOUSEBUTTONUP:
MouseUp(e.button.button);
break;
case SDL.SDL_EventType.SDL_KEYDOWN:
KeyDown(e.key.keysym.sym);
break;
case SDL.SDL_EventType.SDL_KEYUP:
KeyUp(e.key.keysym.sym);
break;
default:
break;
}
}
SDL.SDL_Delay(0);
}
//
// Shut things down nicely.
//
if (_sdlRenderer != IntPtr.Zero)
{
SDL.SDL_DestroyRenderer(_sdlRenderer);
_sdlRenderer = IntPtr.Zero;
}
if (_sdlWindow != IntPtr.Zero)
{
SDL.SDL_DestroyWindow(_sdlWindow);
_sdlWindow = IntPtr.Zero;
}
SDL.SDL_Quit();
}
public void Close()
{
SDL.SDL_Event closeEvent = new SDL.SDL_Event();
closeEvent.type = SDL.SDL_EventType.SDL_QUIT;
SDL.SDL_PushEvent(ref closeEvent);
}
public void DrawDisplayWord(int scanline, int wordOffset, ushort word, bool lowRes)
{
if (lowRes)
{
// Low resolution; double up pixels.
int address = scanline * 76 + wordOffset * 4;
if (address > _1bppDisplayBuffer.Length)
{
throw new InvalidOperationException("Display word address is out of bounds.");
}
UInt32 stretched = StretchWord(word);
_1bppDisplayBuffer[address] = (byte)(stretched >> 24);
_1bppDisplayBuffer[address + 1] = (byte)(stretched >> 16);
_1bppDisplayBuffer[address + 2] = (byte)(stretched >> 8);
_1bppDisplayBuffer[address + 3] = (byte)(stretched);
}
else
{
int address = scanline * 76 + wordOffset * 2;
if (address > _1bppDisplayBuffer.Length)
{
throw new InvalidOperationException("Display word address is out of bounds.");
}
_1bppDisplayBuffer[address] = (byte)(word >> 8);
_1bppDisplayBuffer[address + 1] = (byte)(word);
}
}
/// <summary>
/// Invoked by the DisplayController to draw the cursor at the specified position on the given
/// scanline.
/// </summary>
/// <param name="scanline">The scanline (Y)</param>
/// <param name="xOffset">X offset (in pixels)</param>
/// <param name="cursorWord">The word to be drawn</param>
public void DrawCursorWord(int scanline, int xOffset, bool whiteOnBlack, ushort cursorWord)
{
int address = scanline * 76 + xOffset / 8;
//
// Grab the 32 bits straddling the cursor from the display buffer
// so we can merge the 16 cursor bits in.
//
UInt32 displayWord = (UInt32)((_1bppDisplayBuffer[address] << 24) |
(_1bppDisplayBuffer[address + 1] << 16) |
(_1bppDisplayBuffer[address + 2] << 8) |
_1bppDisplayBuffer[address + 3]);
// Slide the cursor word to the proper X position
UInt32 adjustedCursorWord = (UInt32)(cursorWord << (16 - (xOffset % 8)));
if (!whiteOnBlack)
{
displayWord &= ~adjustedCursorWord;
}
else
{
displayWord |= adjustedCursorWord;
}
_1bppDisplayBuffer[address] = (byte)(displayWord >> 24);
_1bppDisplayBuffer[address + 1] = (byte)(displayWord >> 16);
_1bppDisplayBuffer[address + 2] = (byte)(displayWord >> 8);
_1bppDisplayBuffer[address + 3] = (byte)(displayWord);
}
/// <summary>
/// "Stretches" a 16 bit word into a 32-bit word (for low-res display purposes).
/// </summary>
/// <param name="word"></param>
/// <returns></returns>
private UInt32 StretchWord(ushort word)
{
UInt32 stretched = 0;
for (int i = 0x8000, j = 15; j >= 0; i = i >> 1, j--)
{
uint bit = (uint)(word & i) >> j;
stretched |= (bit << (j * 2 + 1));
stretched |= (bit << (j * 2));
}
return stretched;
}
/// <summary>
/// Transmogrify 1bpp display buffer to 32-bits.
/// </summary>
private void ExpandBitmapToARGB()
{
int rgbIndex = 0;
for (int i = 0; i < _32bppDisplayBuffer.Length / 8; i++)
{
byte b = _1bppDisplayBuffer[i];
for (int bit = 7; bit >= 0; bit--)
{
byte color = (byte)((b & (1 << bit)) == 0 ? 0x00 : 0xff);
_32bppDisplayBuffer[rgbIndex++] = (int)((color == 0) ? 0xff000000 : 0xffffffff);
}
}
}
/// <summary>
/// Invoked when the Alto is done with a field. Tells SDL to redraw the Alto display.
/// This is called on the Alto emulation thread.
/// </summary>
public void Render()
{
//
// Send a render event to the SDL message loop so that things
// will get rendered.
//
SDL.SDL_PushEvent(ref _renderEvent);
}
private void RenderDisplay()
{
//
// Stuff the display data into the display texture
//
ExpandBitmapToARGB();
IntPtr textureBits = IntPtr.Zero;
int pitch = 0;
SDL.SDL_LockTexture(_displayTexture, IntPtr.Zero, out textureBits, out pitch);
Marshal.Copy(_32bppDisplayBuffer, 0, textureBits, _32bppDisplayBuffer.Length);
SDL.SDL_UnlockTexture(_displayTexture);
//
// Render the display texture to the renderer
//
SDL.SDL_RenderCopy(_sdlRenderer, _displayTexture, IntPtr.Zero, IntPtr.Zero);
//
// And show it to us.
//
SDL.SDL_RenderPresent(_sdlRenderer);
}
private void MouseMove(int x, int y)
{
if (!_mouseCaptured)
{
return;
}
if (_skipNextMouseMove)
{
_skipNextMouseMove = false;
return;
}
//
// As with the Winforms mouse capture code...
int mx = _windowWidth / 2;
int my = _windowHeight / 2;
int dx = x - mx;
int dy = y - my;
if (dx != 0 || dy != 0)
{
_system.Mouse.MouseMove(dx, dy);
// Don't handle the very next Mouse Move event (which will just be the motion we caused in the
// below line...)
_skipNextMouseMove = true;
//
// Move the mouse pointer to the middle of the window.
//
SDL.SDL_WarpMouseInWindow(_sdlWindow, mx, my);
}
}
private void KeyDown(SDL.SDL_Keycode code)
{
if (!_mouseCaptured)
{
return;
}
if (_keyMap.ContainsKey(code))
{
_system.Keyboard.KeyDown(_keyMap[code]);
}
}
private void KeyUp(SDL.SDL_Keycode code)
{
//
// Check for Alt key to release mouse
//
if (code == SDL.SDL_Keycode.SDLK_LALT ||
code == SDL.SDL_Keycode.SDLK_RALT)
{
ReleaseMouse();
}
if (!_mouseCaptured)
{
return;
}
if (_keyMap.ContainsKey(code))
{
_system.Keyboard.KeyUp(_keyMap[code]);
}
}
private void MouseDown(byte button, int x, int y)
{
//
// OS X issue: we get mousedown events when the window title
// is clicked, sometimes. So ignore those.
//
if (x < 0 || y < 0)
{
return;
}
if (!_mouseCaptured)
{
CaptureMouse();
}
AltoMouseButton altoButton = GetMouseButton(button);
if (altoButton != AltoMouseButton.None)
{
_system.Mouse.MouseDown(altoButton);
}
}
private void MouseUp(byte button)
{
if (!_mouseCaptured)
{
return;
}
AltoMouseButton altoButton = GetMouseButton(button);
if (altoButton != AltoMouseButton.None)
{
_system.Mouse.MouseUp(altoButton);
}
}
private void CaptureMouse()
{
//
// Turn off the mouse cursor and ensure the mouse is trapped
// within our window.
//
_mouseCaptured = true;
SDL.SDL_ShowCursor(0);
SDL.SDL_SetWindowGrab(_sdlWindow, SDL.SDL_bool.SDL_TRUE);
UpdateWindowTitle();
}
private void ReleaseMouse()
{
//
// Turn the mouse cursor back on, and release the mouse.
//
_mouseCaptured = false;
SDL.SDL_ShowCursor(1);
SDL.SDL_SetWindowGrab(_sdlWindow, SDL.SDL_bool.SDL_FALSE);
UpdateWindowTitle();
}
private void UpdateWindowTitle()
{
SDL.SDL_SetWindowTitle(
_sdlWindow,
String.Format("ContrAlto{0}",
_mouseCaptured ? " - Mouse captured. Press 'Alt' to release." : ""));
}
private void InitSDL()
{
int retVal;
// Get SDL humming
if ((retVal = SDL.SDL_Init(SDL.SDL_INIT_VIDEO)) < 0)
{
throw new InvalidOperationException(String.Format("SDL_Init failed. Error {0:x}", retVal));
}
//
if (SDL.SDL_SetHint(SDL.SDL_HINT_RENDER_SCALE_QUALITY, "1") == SDL.SDL_bool.SDL_FALSE)
{
throw new InvalidOperationException("SDL_SetHint failed to set scale quality.");
}
_sdlWindow = SDL.SDL_CreateWindow(
"ContrAlto",
SDL.SDL_WINDOWPOS_UNDEFINED,
SDL.SDL_WINDOWPOS_UNDEFINED,
_windowWidth,
_windowHeight,
SDL.SDL_WindowFlags.SDL_WINDOW_SHOWN);
if (_sdlWindow == IntPtr.Zero)
{
throw new InvalidOperationException("SDL_CreateWindow failed.");
}
_sdlRenderer = SDL.SDL_CreateRenderer(_sdlWindow, -1, SDL.SDL_RendererFlags.SDL_RENDERER_ACCELERATED);
if (_sdlRenderer == IntPtr.Zero)
{
throw new InvalidOperationException("SDL_CreateRenderer failed.");
}
_displayTexture = SDL.SDL_CreateTexture(
_sdlRenderer,
SDL.SDL_PIXELFORMAT_ARGB8888,
(int)SDL.SDL_TextureAccess.SDL_TEXTUREACCESS_STREAMING,
608,
_windowHeight);
if (_displayTexture == IntPtr.Zero)
{
throw new InvalidOperationException("SDL_CreateTexture failed.");
}
SDL.SDL_SetRenderDrawColor(_sdlRenderer, 0x00, 0x00, 0x00, 0x00);
// Register a User event for rendering.
_renderEventType = SDL.SDL_RegisterEvents(1);
_renderEvent = new SDL.SDL_Event();
_renderEvent.type = (SDL.SDL_EventType)_renderEventType;
}
private void InitKeymap()
{
_keyMap = new Dictionary<SDL.SDL_Keycode, AltoKey>();
_keyMap.Add(SDL.SDL_Keycode.SDLK_a, AltoKey.A);
_keyMap.Add(SDL.SDL_Keycode.SDLK_b, AltoKey.B);
_keyMap.Add(SDL.SDL_Keycode.SDLK_c, AltoKey.C);
_keyMap.Add(SDL.SDL_Keycode.SDLK_d, AltoKey.D);
_keyMap.Add(SDL.SDL_Keycode.SDLK_e, AltoKey.E);
_keyMap.Add(SDL.SDL_Keycode.SDLK_f, AltoKey.F);
_keyMap.Add(SDL.SDL_Keycode.SDLK_g, AltoKey.G);
_keyMap.Add(SDL.SDL_Keycode.SDLK_h, AltoKey.H);
_keyMap.Add(SDL.SDL_Keycode.SDLK_i, AltoKey.I);
_keyMap.Add(SDL.SDL_Keycode.SDLK_j, AltoKey.J);
_keyMap.Add(SDL.SDL_Keycode.SDLK_k, AltoKey.K);
_keyMap.Add(SDL.SDL_Keycode.SDLK_l, AltoKey.L);
_keyMap.Add(SDL.SDL_Keycode.SDLK_m, AltoKey.M);
_keyMap.Add(SDL.SDL_Keycode.SDLK_n, AltoKey.N);
_keyMap.Add(SDL.SDL_Keycode.SDLK_o, AltoKey.O);
_keyMap.Add(SDL.SDL_Keycode.SDLK_p, AltoKey.P);
_keyMap.Add(SDL.SDL_Keycode.SDLK_q, AltoKey.Q);
_keyMap.Add(SDL.SDL_Keycode.SDLK_r, AltoKey.R);
_keyMap.Add(SDL.SDL_Keycode.SDLK_s, AltoKey.S);
_keyMap.Add(SDL.SDL_Keycode.SDLK_t, AltoKey.T);
_keyMap.Add(SDL.SDL_Keycode.SDLK_u, AltoKey.U);
_keyMap.Add(SDL.SDL_Keycode.SDLK_v, AltoKey.V);
_keyMap.Add(SDL.SDL_Keycode.SDLK_w, AltoKey.W);
_keyMap.Add(SDL.SDL_Keycode.SDLK_x, AltoKey.X);
_keyMap.Add(SDL.SDL_Keycode.SDLK_y, AltoKey.Y);
_keyMap.Add(SDL.SDL_Keycode.SDLK_z, AltoKey.Z);
_keyMap.Add(SDL.SDL_Keycode.SDLK_0, AltoKey.D0);
_keyMap.Add(SDL.SDL_Keycode.SDLK_1, AltoKey.D1);
_keyMap.Add(SDL.SDL_Keycode.SDLK_2, AltoKey.D2);
_keyMap.Add(SDL.SDL_Keycode.SDLK_3, AltoKey.D3);
_keyMap.Add(SDL.SDL_Keycode.SDLK_4, AltoKey.D4);
_keyMap.Add(SDL.SDL_Keycode.SDLK_5, AltoKey.D5);
_keyMap.Add(SDL.SDL_Keycode.SDLK_6, AltoKey.D6);
_keyMap.Add(SDL.SDL_Keycode.SDLK_7, AltoKey.D7);
_keyMap.Add(SDL.SDL_Keycode.SDLK_8, AltoKey.D8);
_keyMap.Add(SDL.SDL_Keycode.SDLK_9, AltoKey.D9);
_keyMap.Add(SDL.SDL_Keycode.SDLK_SPACE, AltoKey.Space);
_keyMap.Add(SDL.SDL_Keycode.SDLK_PERIOD, AltoKey.Period);
_keyMap.Add(SDL.SDL_Keycode.SDLK_COMMA, AltoKey.Comma);
_keyMap.Add(SDL.SDL_Keycode.SDLK_QUOTE, AltoKey.Quote);
_keyMap.Add(SDL.SDL_Keycode.SDLK_BACKSLASH, AltoKey.BSlash);
_keyMap.Add(SDL.SDL_Keycode.SDLK_SLASH, AltoKey.FSlash);
_keyMap.Add(SDL.SDL_Keycode.SDLK_EQUALS, AltoKey.Plus);
_keyMap.Add(SDL.SDL_Keycode.SDLK_UNDERSCORE, AltoKey.Minus);
_keyMap.Add(SDL.SDL_Keycode.SDLK_ESCAPE, AltoKey.ESC);
_keyMap.Add(SDL.SDL_Keycode.SDLK_DELETE, AltoKey.DEL);
_keyMap.Add(SDL.SDL_Keycode.SDLK_LEFT, AltoKey.Arrow);
_keyMap.Add(SDL.SDL_Keycode.SDLK_LSHIFT, AltoKey.LShift);
_keyMap.Add(SDL.SDL_Keycode.SDLK_RSHIFT, AltoKey.RShift);
_keyMap.Add(SDL.SDL_Keycode.SDLK_LCTRL, AltoKey.CTRL);
_keyMap.Add(SDL.SDL_Keycode.SDLK_RCTRL, AltoKey.CTRL);
_keyMap.Add(SDL.SDL_Keycode.SDLK_RETURN, AltoKey.Return);
_keyMap.Add(SDL.SDL_Keycode.SDLK_F1, AltoKey.BlankTop);
_keyMap.Add(SDL.SDL_Keycode.SDLK_F2, AltoKey.BlankMiddle);
_keyMap.Add(SDL.SDL_Keycode.SDLK_F3, AltoKey.BlankBottom);
_keyMap.Add(SDL.SDL_Keycode.SDLK_F4, AltoKey.Lock);
_keyMap.Add(SDL.SDL_Keycode.SDLK_BACKSPACE, AltoKey.BS);
_keyMap.Add(SDL.SDL_Keycode.SDLK_TAB, AltoKey.TAB);
_keyMap.Add(SDL.SDL_Keycode.SDLK_SEMICOLON, AltoKey.Semicolon);
_keyMap.Add(SDL.SDL_Keycode.SDLK_LEFTBRACKET, AltoKey.LBracket);
_keyMap.Add(SDL.SDL_Keycode.SDLK_RIGHTBRACKET, AltoKey.RBracket);
_keyMap.Add(SDL.SDL_Keycode.SDLK_DOWN, AltoKey.LF);
}
private static AltoMouseButton GetMouseButton(byte button)
{
AltoMouseButton altoButton = AltoMouseButton.None;
switch (button)
{
case 1:
altoButton = AltoMouseButton.Left;
break;
case 2:
altoButton = AltoMouseButton.Middle;
break;
case 3:
altoButton = AltoMouseButton.Right;
break;
}
return altoButton;
}
private AltoSystem _system;
private IntPtr _sdlWindow = IntPtr.Zero;
private IntPtr _sdlRenderer = IntPtr.Zero;
private UInt32 _renderEventType;
private SDL.SDL_Event _renderEvent;
//
// Display data
//
private const int _windowWidth = 608;
private const int _windowHeight = 808;
// Rendering textures
private IntPtr _displayTexture = IntPtr.Zero;
//
// Buffer for display pixels. This is 1bpp, directly written by the Alto.
//
private byte[] _1bppDisplayBuffer = new byte[808 * 76 + 4]; // + 4 (32-bits) to make cursor display logic simpler.
// and 608 pixels wide to make it a multiple of 8 bits.
//
// Buffer for rendering pixels. SDL doesn't support 1bpp pixel formats, so to keep things simple we use
// an array of ints and a 32bpp format. What's a few extra bits between friends.
//
private int[] _32bppDisplayBuffer = new int[(808 * 608 + 8)]; // + 8 (32-bits) as above.
// and 608 pixels wide as above.
//
// Keyboard data
//
private Dictionary<SDL.SDL_Keycode, AltoKey> _keyMap;
//
// Mouse data
//
private bool _mouseCaptured;
private bool _skipNextMouseMove;
}
}

View File

@@ -0,0 +1,369 @@
/*
This file is part of ContrAlto.
ContrAlto is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ContrAlto is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with ContrAlto. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Threading;
namespace Contralto.SdlUI
{
public enum CommandResult
{
Normal,
Quit
}
/// <summary>
/// Provides a command-line interface to ContrAlto controls,
/// as a substitute for the GUI interface of the Windows version.
/// </summary>
public class SdlConsole
{
public SdlConsole(AltoSystem system)
{
_system = system;
_controller = new ExecutionController(_system);
_controller.ErrorCallback += OnExecutionError;
}
/// <summary>
/// Invoke the CLI loop in a separate thread.
/// </summary>
public void Run(SdlAltoWindow mainWindow)
{
Console.WriteLine("You are at the ContrAlto console. Type 'show commands' to see");
Console.WriteLine("a list of possible commands, and hit Tab to see possible command completions.");
_mainWindow = mainWindow;
_mainWindow.OnClosed += OnMainWindowClosed;
_cliThread = new Thread(RunCliThread);
_cliThread.Start();
}
/// <summary>
/// The CLI thread
/// </summary>
private void RunCliThread()
{
ConsoleExecutor executor = new ConsoleExecutor(this);
CommandResult state = CommandResult.Normal;
while (state != CommandResult.Quit)
{
state = executor.Prompt();
}
//
// Ensure the emulator is stopped.
//
_controller.StopExecution();
//
// Ensure the main window is closed.
//
_mainWindow.Close();
}
private void OnMainWindowClosed(object sender, EventArgs e)
{
//
// Make sure the emulator is stopped.
//
_controller.StopExecution();
_system.Shutdown();
//
// The Alto window was closed, shut down the CLI.
//
_cliThread.Abort();
}
/// <summary>
/// Error handling
/// </summary>
/// <param name="e"></param>
private void OnExecutionError(Exception e)
{
Console.WriteLine("Execution error: {0} - {1}", e.Message, e.StackTrace);
System.Diagnostics.Debugger.Break();
}
[DebuggerFunction("quit", "Exits ContrAlto.")]
private CommandResult Quit()
{
_controller.StopExecution();
return CommandResult.Quit;
}
//
// Console commands
//
[DebuggerFunction("start", "Starts the emulated Alto normally.")]
private CommandResult Start()
{
if (_controller.IsRunning)
{
Console.WriteLine("Alto is already running.");
}
else
{
_controller.StartExecution(AlternateBootType.None);
Console.WriteLine("Alto started.");
}
return CommandResult.Normal;
}
[DebuggerFunction("stop", "Stops the emulated Alto.")]
private CommandResult Stop()
{
_controller.StopExecution();
Console.WriteLine("Alto stopped.");
return CommandResult.Normal;
}
[DebuggerFunction("reset", "Resets the emulated Alto.")]
private CommandResult Reset()
{
_controller.Reset(AlternateBootType.None);
Console.WriteLine("Alto reset.");
return CommandResult.Normal;
}
[DebuggerFunction("start with keyboard disk boot", "Starts the emulated Alto with the specified keyboard disk boot address.")]
private CommandResult StartDisk()
{
if (_controller.IsRunning)
{
Console.WriteLine("Alto is already running. Use 'stop' to stop the Alto first.");
}
else
{
_controller.StartExecution(AlternateBootType.Disk);
}
return CommandResult.Normal;
}
[DebuggerFunction("start with keyboard net boot", "Starts the emulated Alto with the specified keyboard ethernet boot number.")]
private CommandResult StartNet()
{
if (_controller.IsRunning)
{
Console.WriteLine("Alto is already running. Use 'stop' to stop the Alto first.");
}
else
{
_controller.StartExecution(AlternateBootType.Ethernet);
}
return CommandResult.Normal;
}
[DebuggerFunction("load disk", "Loads the specified drive with the requested disk image.", "<drive> <path>")]
private CommandResult LoadDisk(ushort drive, string path)
{
if (drive > 1)
{
throw new InvalidOperationException("Drive specification out of range.");
}
// Save current drive contents.
_system.CommitDiskPack(drive);
// Load the new pack.
_system.LoadDrive(drive, path);
Console.WriteLine("Drive {0} loaded.", drive);
return CommandResult.Normal;
}
[DebuggerFunction("unload disk", "Unloads the specified drive.", "<drive>")]
private CommandResult UnloadDisk(ushort drive)
{
if (drive > 1)
{
throw new InvalidOperationException("Drive specification out of range.");
}
// Save current drive contents.
_system.CommitDiskPack(drive);
// Unload the current pack.
_system.UnloadDrive(drive);
Console.WriteLine("Drive {0} unloaded.", drive);
return CommandResult.Normal;
}
[DebuggerFunction("show disk", "Displays the contents of the specified drive.", "<drive>")]
private CommandResult ShowDisk(ushort drive)
{
if (drive > 1)
{
throw new InvalidOperationException("Drive specification out of range.");
}
// Save current drive contents.
if (_system.DiskController.Drives[drive].IsLoaded)
{
Console.WriteLine("Drive {0} contains image {1}",
drive,
_system.DiskController.Drives[drive].Pack.PackName);
}
else
{
Console.WriteLine("Drive {0} is not loaded.", drive);
}
return CommandResult.Normal;
}
[DebuggerFunction("show system type", "Displays the Alto system type.")]
private CommandResult ShowSystemType()
{
Console.WriteLine("System type is {0}", Configuration.SystemType);
return CommandResult.Normal;
}
[DebuggerFunction("set ethernet address", "Sets the Alto's host Ethernet address.")]
private CommandResult SetEthernetAddress(byte address)
{
if (address == 0 || address == 0xff)
{
Console.WriteLine("Address {0} is invalid.", Conversion.ToOctal(address));
}
else
{
Configuration.HostAddress = address;
}
return CommandResult.Normal;
}
[DebuggerFunction("show ethernet address", "Displays the Alto's host Ethernet address.")]
private CommandResult ShowEthernetAddress()
{
Console.WriteLine("Ethernet address is {0}", Conversion.ToOctal(Configuration.HostAddress));
return CommandResult.Normal;
}
[DebuggerFunction("show host network interface name", "Displays the host network interface used for Ethernet emulation")]
private CommandResult ShowHostNetworkInterfaceName()
{
Console.WriteLine("Network interface is '{0}'", Configuration.HostPacketInterfaceName);
return CommandResult.Normal;
}
[DebuggerFunction("show host network interface type", "Displays the host network interface type (RAW or UDP)")]
private CommandResult ShowHostNetworkInterfaceType()
{
Console.WriteLine("Network interface type is '{0}'", Configuration.HostPacketInterfaceType);
return CommandResult.Normal;
}
[DebuggerFunction("set keyboard net boot file", "Sets the boot file used for net booting.")]
private CommandResult SetKeyboardBootFile(ushort file)
{
Configuration.BootFile = file;
return CommandResult.Normal;
}
[DebuggerFunction("set keyboard disk boot address", "Sets the boot address used for disk booting.")]
private CommandResult SetKeyboardBootAddress(ushort address)
{
Configuration.BootFile = address;
return CommandResult.Normal;
}
// Not yet supported on non-Windows platforms
/*
[DebuggerFunction("enable display interlacing", "Enables interlaced display.")]
private CommandResult EnableDisplayInterlacing()
{
Configuration.InterlaceDisplay = true;
return CommandResult.Normal;
}
[DebuggerFunction("disable display interlacing", "Disables interlaced display.")]
private CommandResult DisableDisplayInterlacing()
{
Configuration.InterlaceDisplay = false;
return CommandResult.Normal;
}
[DebuggerFunction("enable speed throttling", "Limits execution speed to 60 fields/sec.")]
private CommandResult EnableSpeedThrottling()
{
Configuration.ThrottleSpeed = true;
return CommandResult.Normal;
}
[DebuggerFunction("disable speed throttling", "Removes speed limits.")]
private CommandResult DisableSpeedThrottling()
{
Configuration.ThrottleSpeed = false;
return CommandResult.Normal;
}
[DebuggerFunction("enable audio dac", "Enables the Audio DAC.")]
private CommandResult EnableAudioDAC()
{
Configuration.EnableAudioDAC = true;
return CommandResult.Normal;
}
[DebuggerFunction("disable audio dac", "Disables the Audio DAC.")]
private CommandResult DisableAudioDAC()
{
Configuration.EnableAudioDAC = false;
return CommandResult.Normal;
}
[DebuggerFunction("enable audio capture", "Enables capture of DAC output.")]
private CommandResult EnableAudioDACCapture()
{
Configuration.EnableAudioDACCapture = true;
return CommandResult.Normal;
}
[DebuggerFunction("disable audio capture", "Disables capture of DAC output.")]
private CommandResult DisableAudioDACCapture()
{
Configuration.EnableAudioDACCapture = false;
return CommandResult.Normal;
}
[DebuggerFunction("set audio capture path", "Configures the path for capture output.")]
private CommandResult SetAudioCapturePath(string path)
{
Configuration.AudioDACCapturePath = path;
return CommandResult.Normal;
}
*/
private AltoSystem _system;
private ExecutionController _controller;
private SdlAltoWindow _mainWindow;
private Thread _cliThread;
}
}

View File

@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="NAudio" version="1.8.0" targetFramework="net452" />
<package id="Pcap.Net.x86" version="1.0.4.1" targetFramework="net452" />
<package id="SDL2-CS.dll" version="2.0.0.0" targetFramework="net452" />
</packages>

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -1,110 +0,0 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>PcapDotNet.Core.Extensions</name>
</assembly>
<members>
<member name="T:PcapDotNet.Core.Extensions.PacketCommunicatorExtensions">
<summary>
Different extension methods for PacketCommunicator class.
<seealso cref="T:PcapDotNet.Core.PacketCommunicator"/>
</summary>
</member>
<member name="M:PcapDotNet.Core.Extensions.PacketCommunicatorExtensions.ReceivePackets(PcapDotNet.Core.PacketCommunicator,System.Int32)">
<summary>
Collect a group of packets.
Similar to ReceivePackets() except instead of calling a callback the packets are returned as an IEnumerable.
<seealso cref="M:PcapDotNet.Core.PacketCommunicator.ReceivePackets(System.Int32,PcapDotNet.Core.HandlePacket)"/>
<seealso cref="M:PcapDotNet.Core.PacketCommunicator.ReceiveSomePackets(System.Int32@,System.Int32,PcapDotNet.Core.HandlePacket)"/>
</summary>
<param name="communicator">The PacketCommunicator to work on</param>
<param name="count">Number of packets to process. A negative count causes ReceivePackets() to loop until the IEnumerable break (or until an error occurs).</param>
<returns>An IEnumerable of Packets to process.</returns>
<exception cref="T:System.InvalidOperationException">Thrown if the mode is not Capture or an error occurred.</exception>
<remarks>
<para>Only the first bytes of data from the packet might be in the received packet (which won't necessarily be the entire packet; to capture the entire packet, you will have to provide a value for snapshortLength in your call to PacketDevice.Open() that is sufficiently large to get all of the packet's data - a value of 65536 should be sufficient on most if not all networks).</para>
<para>If a break is called on the returned Enumerable before the number of packets asked for received, the packet that was handled while breaking the enumerable may not be the last packet read. More packets might be read. This is because this method actually loops over calls to ReceiveSomePackets()</para>
</remarks>
</member>
<member name="M:PcapDotNet.Core.Extensions.PacketCommunicatorExtensions.ReceivePackets(PcapDotNet.Core.PacketCommunicator)">
<summary>
Collect a group of packets.
Similar to ReceivePackets() except instead of calling a callback the packets are returned as an IEnumerable.
Loops until the IEnumerable break (or until an error occurs).
<seealso cref="M:PcapDotNet.Core.PacketCommunicator.ReceivePackets(System.Int32,PcapDotNet.Core.HandlePacket)"/>
<seealso cref="M:PcapDotNet.Core.PacketCommunicator.ReceiveSomePackets(System.Int32@,System.Int32,PcapDotNet.Core.HandlePacket)"/>
</summary>
<param name="communicator">The PacketCommunicator to work on</param>
<returns>An IEnumerable of Packets to process.</returns>
<exception cref="T:System.InvalidOperationException">Thrown if the mode is not Capture or an error occurred.</exception>
<remarks>
<para>Only the first bytes of data from the packet might be in the received packet (which won't necessarily be the entire packet; to capture the entire packet, you will have to provide a value for snapshortLength in your call to PacketDevice.Open() that is sufficiently large to get all of the packet's data - a value of 65536 should be sufficient on most if not all networks).</para>
<para>If a break is called on the returned Enumerable, the packet that was handled while breaking the enumerable may not be the last packet read. More packets might be read. This is because this method actually loops over calls to ReceiveSomePackets()</para>
</remarks>
</member>
<member name="T:PcapDotNet.Core.Extensions.LivePacketDeviceExtensions">
<summary>
Extension methods for LivePacketDevice class.
</summary>
</member>
<member name="M:PcapDotNet.Core.Extensions.LivePacketDeviceExtensions.GetGuid(PcapDotNet.Core.LivePacketDevice)">
<summary>
Returns the GUID (NetCfgInstanceId) for a <see cref="T:PcapDotNet.Core.LivePacketDevice"/> instance.
The GUID is parsed from the <see cref="P:PcapDotNet.Core.LivePacketDevice.Name"/> property.
</summary>
<param name="livePacketDevice">The <see cref="T:PcapDotNet.Core.LivePacketDevice"/> instance.</param>
<returns>The GUID (NetCfgInstanceId) of the <see cref="T:PcapDotNet.Core.LivePacketDevice"/> instance.</returns>
<exception cref="T:System.InvalidOperationException">When the <see cref="P:PcapDotNet.Core.LivePacketDevice.Name"/> doesn't match the expectations.</exception>
</member>
<member name="M:PcapDotNet.Core.Extensions.LivePacketDeviceExtensions.GetPnpDeviceId(PcapDotNet.Core.LivePacketDevice)">
<summary>
Returns the PNPDeviceID for a <see cref="T:PcapDotNet.Core.LivePacketDevice"/> instance.
The PNPDeviceID is retrieved by querying the registry.
</summary>
<param name="livePacketDevice">The <see cref="T:PcapDotNet.Core.LivePacketDevice"/> instance.</param>
<returns>The PNPDeviceID of the <see cref="T:PcapDotNet.Core.LivePacketDevice"/> instance.</returns>
<exception cref="T:System.InvalidOperationException">When the PNPDeviceID cannot be retrieved from the registry.</exception>
</member>
<member name="M:PcapDotNet.Core.Extensions.LivePacketDeviceExtensions.GetNetworkInterface(PcapDotNet.Core.LivePacketDevice)">
<summary>
Returns the network interface of the packet device.
The interface is found using its id.
If no interface is found, null is returned.
</summary>
<param name="livePacketDevice">The LivePacketDevice to look for a matching network interface for.</param>
<returns>The network interface found according to the given device or null if none is found.</returns>
</member>
<member name="M:PcapDotNet.Core.Extensions.LivePacketDeviceExtensions.GetMacAddress(PcapDotNet.Core.LivePacketDevice)">
<summary>
Returns the <see cref="T:PcapDotNet.Packets.Ethernet.MacAddress"/> of the network interface of the given device.
If no interface matches the given packet device, an exception is thrown.
We first look for the device using <see cref="M:System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces"/> and if that fails we look for them using WMI.
</summary>
<param name="livePacketDevice">The packet device to look for the matching interface.</param>
<returns>The <see cref="T:PcapDotNet.Packets.Ethernet.MacAddress"/> of the given device's matching interface.</returns>
</member>
<member name="M:PcapDotNet.Core.Extensions.LivePacketDeviceExtensions.GetMacAddressWmi(PcapDotNet.Core.LivePacketDevice)">
<summary>
Returns the <see cref="T:PcapDotNet.Packets.Ethernet.MacAddress"/> for a <see cref="T:PcapDotNet.Core.LivePacketDevice"/> instance.
The <see cref="T:PcapDotNet.Packets.Ethernet.MacAddress"/> is retrieved through using WMI.
</summary>
<param name="livePacketDevice">The <see cref="T:PcapDotNet.Core.LivePacketDevice"/> instance.</param>
<returns>The <see cref="T:PcapDotNet.Packets.Ethernet.MacAddress"/> of the <see cref="T:PcapDotNet.Core.LivePacketDevice"/> instance.</returns>
<exception cref="T:System.InvalidOperationException">When the <see cref="T:PcapDotNet.Packets.Ethernet.MacAddress"/> cannot be retrieved using WMI.</exception>
</member>
<member name="T:PcapDotNet.Core.Extensions.NetworkInterfaceExtensions">
<summary>
Extension methods for NetworkInterface class.
</summary>
</member>
<member name="M:PcapDotNet.Core.Extensions.NetworkInterfaceExtensions.GetLivePacketDevice(System.Net.NetworkInformation.NetworkInterface)">
<summary>
Returns the LivePacketDevice of the given NetworkInterface.
The LivePacketDevice is found using the NetworkInterface's id and the LivePacketDevice's name.
If no interface is found, null is returned.
</summary>
<param name="networkInterface">The NetworkInterface to look for a matching LivePacketDevice for.</param>
<returns>The LivePacketDevice found according to the given NetworkInterface or null if none is found.</returns>
</member>
</members>
</doc>

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

460
Contralto/readme-mono.txt Normal file
View File

@@ -0,0 +1,460 @@
Readme-mono.txt for Contralto v1.2:
1. Introduction and Overview
============================
ContrAlto aspires to be a faithful emulation of the Xerox Alto II series of
pioneering graphical workstations developed at Xerox PARC in 1973.
This document covers installation and use on Unix and Mac OS platforms under
the Mono runtime. See readme.txt for instructions for Windows platforms.
1.1 What's Emulated
-------------------
ContrAlto currently emulates the following Alto hardware:
- Alto I and Alto II XM CPU
- Microcode RAM (in 1K RAM, 1K RAM/2K ROM, or 3K RAM configurations)
- 256KW of main memory (in 64KW banks)
- Two Diablo Model 31 or 44 drives
- Ethernet (encapsulated in UDP datagrams on the host machine)
- Standard Keyboard/Mouse/Video
- Audio DAC (used with the Smalltalk Music System)
1.2 What's Not
--------------
At this time, ContrAlto does not support more exotic hardware such as Trident
disks, printers or audio using the utility port, the Orbit printer interface, or the
keyset input device.
The Audio DAC is technically emulated, but output is not connected to an audio
device on non-Windows platforms at this time.
2.0 Requirements
================
ContrAlto is a .NET application and will run under Mono (http://www.mono-project.com/)
on Unix and MacOS.
Ensure you have the latest Mono environment installed on your system. ContrAlto was
developed and tested on 5.0.1 and while it will work on earlier versions, it has not
been well tested on them. You can find download links and instructions at
http://www.mono-project.com/download/.
Additionally, ContrAlto relies on SDL (Simple Directmedia Layer) 2.0 for the Alto's
display, keyboard and mouse. On OS X, the native SDL library is included with the
ContrAlto archive. On Linux, you will want to ensure that the SDL 2.0 libraries
built for your distribution are installed.
A three-button mouse is essential for using most Alto software. On most mice,
the mousewheel can be clicked to provide the third (middle) button. Laptops
with trackpads may have configuration options to simulate multiple buttons but
will likely be clumsy to use.
3.0 Getting Started
===================
Installation of ContrAlto on Unix and Mac OS machines is straightforward --
unpack the ContrAlto-Mono.zip archive to a directory on your machine (a
subdirectory of your home directory will work fine). Ensure you've installed
the prerequisites outlined in Section 2.0 and you should be good to go.
3.1 Starting the Alto
=====================
To launch ContrAlto, run "mono ContrAlto.exe" from a terminal window. A window
for the Alto's display labeled "ContrAlto" will appear -- this is where you will
interact with your virtual Alto. (Note that on the first run of ContrAlto, it may
take several seconds for this window to appear.) The terminal window will provide
you with a command-line console for configuring and controlling the emulated Alto.
On a real Alto, the system is booted by loading a 14" disk pack into the front
of a Diablo 31 drive, waiting for it to spin up for 20 seconds and then
pressing the "Reset" button on the back of the keyboard.
Booting an emulated Alto under ContrAlto is slightly less time-consuming.
To load a disk pack into the virtual Diablo drive, you use the "Load Disk"
command at the console -- this allows selection of the disk image (effectively
a "virtual disk pack") to be loaded. Disk images are not included with ContrAlto
but may be found in various places on the Internet -- see Section 3.1.3 for details.
For example "Load Disk 0 Smalltalk.dsk" will load a disk image named Smalltalk.dsk
into drive 0.
Once the pack has been loaded, you can start the Alto by using the "Start" command
at the console. The display will turn white and after 5-10 seconds a mouse cursor
will appear, followed shortly by the banner of the Xerox Alto Executive.
Congratulations, your Alto is now running! Click on the display window to start
interacting with it using the keyboard and mouse (and if you need your mouse back
for other things, press either "Alt" key on your keyboard.) See Section 3.1 for
details on using ContrAlto.
3.1 Using the Alto
==================
3.1.1 Mouse
-----------
ContrAlto uses your computer's mouse to simulate the one the Alto uses.
In order to accurately simulate the mouse, ContrAlto must "capture" the real
mouse, which effectively makes your system's mouse exclusive to the ContrAlto
window. (If you've ever used virtualization software like VMWare, VirtualBox,
Virtual PC, or Parallels, you may be familiar with this behavior.)
Clicking on the ContrAlto display window will cause ContrAlto to capture the
mouse. Once the mouse has been captured, any mouse movements will be reflected
by the Alto's mouse cursor. While ContrAlto has control of the mouse, you will
not be able to use the mouse for other programs running on your computer. To release
ContrAlto's control, press either "Alt" key (the "Option" key on Macintosh)
on your keyboard. Mouse movements will return to normal, and you will not be
able to control the Alto's mouse or keyboard until you click on the ContrAlto
display window again.
The Alto mouse is a three-button mouse. Alto mouse buttons are mapped as you
would expect. If you have a real three-button mouse then this is completely
straightforward. If you have a two button mouse with a "mousewheel" then
a mousewheel click maps to a click of the Alto's middle mouse button.
If you have a trackpad or other pointing device, using the middle mouse button
may be more complicated. See what configuration options your operating system
and/or drivers provide you for mapping mouse buttons.
3.1.2 Keyboard
--------------
ContrAlto emulates the 61-key Alto II keyboard. The vast majority of keys
(the alphanumerics and punctuation) work as you would expect them to, but the
Alto has a few special keys, which are described below:
Alto Key PC Key
-------- ----------
LF Down Arrow
BS Backspace
Blank-Top F1
Blank-Middle F2
Blank-Bottom F3
<- (arrow) Left Arrow
DEL Del
LOCK F4
3.1.3 Disk Packs
----------------
A real Alto uses large 14" disk packs for disk storage, each containing
approximately 2.5 megabytes (for Diablo 31) or 5 megabytes (for Diablo 44) of
data. ContrAlto uses files, referred to as "disk images" or just "images"
that contain a bit-for-bit copy of these original packs. These are a lot
easier to use with a modern PC.
Disk images can be loaded and unloaded via the "Load Disk" command. (See Section
5 for details on this and other commands.)
If you modify the contents of a loaded disk (for example creating new files or
deleting existing ones) the changes will be written back out to the disk image
when a new image is loaded or when ContrAlto exits. For this reason it may be
a good idea to make backups of packs from time to time (just like on the real
machine.)
ContrAlto does not come with any disk images, however an assortment of Alto
programs can be found on Bitsavers.org, at
http://www.bitsavers.org/bits/Xerox/Alto/disk_images/. Images include:
AllGames.dsk - A collection of games and toys for the Alto
Bcpl.dsk - A set of BCPL development tools
Diags.dsk - Diagnostic tools
NonProg.dsk - The "Non-Programmer's Disk," containing Bravo
Xmsmall.dsk - Smalltalk-76
3.1.4 Startup, Reset and Shutdown
---------------------------------
The system can be started at any time by using the "Start" command, though
in general having a pack image loaded first is a good idea. Similarly, the
"Reset" command will reset the Alto.
You can shut down the Alto by closing the ContrAlto window or using the
"Quit" console command. Either will commit disk changes made to the
currently loaded disks back to the disk image files before exiting.
However, you will want to be sure the software running on the Alto is ready
to be shutdown first, or else you may lose work or corrupt your disk.
3.2 Additional Reading Materials
----------------------------------
The Bitsavers Alto archive at http://http://bitsavers.org/pdf/xerox/alto is an
excellent repository of original Alto documentation, here are a few documents to
get you started:
- The "Alto User's Handbook" is indispensable and contains an overview of the
Alto Executive (the OS "shell"), Bravo (great-granddaddy of Microsoft Word)
and other utilities.
http://bitsavers.org/pdf/xerox/alto/Alto_Users_Handbook_Sep79.pdf
- "Alto Subsystems" documents many of the common Alto programs and tools
("subsystems" in Alto parlance) in detail.
http://bitsavers.org/pdf/xerox/alto/AltoSubsystems_Oct79.pdf
- "Alto Operating System Reference Manual" is useful if you are going to do
any programming for the Alto.
http://bitsavers.org/pdf/xerox/alto/AltoSWRef.part1.pdf
http://bitsavers.org/pdf/xerox/alto/AltoSWRef.part2.pdf
- "BCPL Reference Manual" is definitely required if you are going to do any
programming on the Alto (in BCPL, anyway...)
http://bitsavers.org/pdf/xerox/alto/bcpl/AltoBCPLdoc.pdf
- "Bravo Course Outline" is a tutorial that will show you how to use the Bravo
editor.
http://bitsavers.org/pdf/xerox/alto/BravoCourse.pdf
- The "Alto Hardware Manual" is fun to read through if you're planning on
writing an Alto emulator of your own. If you're into that sort of thing.
http://bitsavers.org/pdf/xerox/alto/AltoHWRef.part1.pdf
http://bitsavers.org/pdf/xerox/alto/AltoHWRef.part2.pdf
- "A Field Guide to Alto-Land" is a casual perspective on Alto use (and
the culture that grew around it) at Xerox PARC.
http://xeroxalto.computerhistory.org/_cd8_/altodocs/.fieldguide.press!2.pdf
4.0 Configuration
=================
ContrAlto can be configured through the use of configuration files. These
are simple text files with sets of parameters and their values in the form:
ParameterName = Value
By default, ContrAlto looks for configuration data in a file named
ContrAlto.cfg. Alternate configuration files can be specified as a
command-line argument at startup via:
mono ContrAlto.exe <configuration file>
An example configuration file looks something like:
# contralto.cfg:
#
# This file contains configuration parameters for ContrAlto.
# All integers are specified in octal.
#
# System configuration
SystemType = TwoKRom
HostAddress = 42
# Disk configuration
Disk0Image = alto.dsk
Disk1Image = bcpl.dsk
# Host networking configuration
HostPacketInterfaceType = UDPEncapsulation
HostPacketInterfaceName = eth0
# Emulation Options
BootAddress = 0
BootFile = 0
AlternateBootType = Ethernet
The following parameters are configurable:
SystemType: Selects the type of Alto system to emulate. One of:
- AltoI : an Alto I, with 64KW memory, 1K ROM, and 1K CRAM
- OneKRom : an Alto II XM system with 1K ROM, 1K CRAM
- TwoKRom : an Alto II XM system with 2K ROM, 1K CRAM
- ThreeKRam : an Alto II XM system with 1K ROM, 3K CRAM
The default is TwoKRom.
HostAddress: Specifies the Alto's Ethernet address (in octal). Any value
between 1 and 376 is allowed.
Drive0Image and Drive1Image: Specifies a disk image to be loaded into the
specified drive. These parameters are optional.
HostPacketInterfaceType: Specifies the type of interface to be used on the
host for Ethernet emulation. One of:
- UDPEncapsulation: Transmits Alto Ethernet packets over UDP broadcasts
- RAWEncapsulation: Transmits Alto Ethernet packets over raw Ethernet packets.
(not yet available on Unix / OS X platforms.)
- None: No packet encapsulation.
HostPacketInterfaceName: Specifies the name of the host network interface
to use for Ethernet emulation. (e.g. "eth0"") If no network
interface is to be used, this parameter can be omitted.
BootAddress: The address to use with a Keyboard Disk Boot (See section 5.0)
BootFile: The file number to use with a Keyboard Net Boot (again, Section 5.0)
AlternateBootType: The type of boot to default to (Section 5.0)
5.0 Console Interface
=====================
After startup, you will be at the ContrAlto console prompt (a '>' character).
ContrAlto provides a somewhat-context-sensitive input line. Press TAB at any
point during input to see possible completions for the command you're entering.
The "show commands" command provides a brief synopsis of available commands,
these are described in greater detail in Section 5.1.
All numeric arguments are specified in Octal by default. A number may be
prefixed with 'b', 'o', 'd', or 'x' to specify binary, octal, decimal or
hexadecimal, respectively.
All numeric outputs are presented in Octal.
At any point while the emulator is running is running the console is active
and commands may be entered.
5.1 Console Commands
--------------------
Quit - Exits ContrAlto. Any modifications to loaded disk images are saved.
Start - Starts the emulated Alto system.
Stop - Stops the emulated Alto.
Reset - Resets the emulated Alto.
Start With Keyboard Disk Boot - Starts the emulated Alto with the keyboard disk boot address specified
either in the configuration file or by the Set Keyboard Disk Boot Address
command.
Start With Keyboard Net Boot - Starts the emulated Alto with the keyboard ethernet boot number specified
either in the configuration file or by the Set Keyboard Net Boot File
command.
Load Disk <drive> <path> - Loads the specified drive (0 or 1) with the requested disk image.
Unload Disk <drive> - Unloads the specified drive (0 or 1). Changes to disk contents are saved.
Show Disk <drive> - Displays the currently loaded image for the specified drive (0 or 1).
Show System Type - Displays the Alto system type as configured by the configuration file.
Set Ethernet Address - Sets the Alto's host Ethernet address. Values between 1 and 376 (octal) are
allowed.
Show Ethernet Address - Displays the Alto's host Ethernet address.
Show Host Network Interface Name - Displays the host network interface used for Ethernet emulation.
Show Host Network Interface Type - Displays the host network interface type.
Set Keyboard Net Boot File - Sets the boot file used for net booting. Values between 0 and 177777
are allowed.
Set Keyboard Disk Boot Address - Sets the boot address used for disk booting. Values between 0 and
177777 are allowed.
Show Commands - Shows debugger commands and their descriptions.
5.2 Alternate ("keyboard") Boots
--------------------------------
The Alto allowed the specification of alternate boot addresses by holding down
a set of keys on the keyboard while hitting the "reset" switch on the back of
the keyboard. Since this would be difficult to pull off by hand on the emulator
due to the UI involved, ContrAlto provides a set of console commands to select the
alternate address to boot. When the "Start With Keyboard Net|Disk Boot" command is
used, the system will be started (or restarted) with these keys held down on
your behalf.
Ethernet booting will only work if another host on the network is providing boot services.
The "Set Keyboard Disk Boot Address" command accepts a 16-bit octal value (from 0 to 177777)
specifying the address to be booted from disk.
The "Set Keyboard Net Boot Address" command accepts a 16-bit octal value (from 0 to 177777)
specifying the file to be net booted.
6.0 Known Issues
================
At the moment, the following issues are known and being worked on. If you find
an issue not listed here, see section 7.0 to report a new bug.
- Smalltalk-80 does not run.
- Audio and RAW ethernet packets are not available on Unix / OS X.
7.0 Reporting Bugs
==================
If you believe you have found a new issue (or have a feature request) please
send an e-mail to joshd@livingcomputers.org with a subject line starting
with "ContrAlto Bug".
When you send a report, please be as specific and detailed as possible:
- What issue are you seeing?
- What Alto software are you running?
- What operating system are you running ContrAlto on?
- What are the exact steps needed to reproduce the issue?
The more detailed the bug report, the more possible it is for me to track down
the cause.
8.0 Source Code
===============
The complete source code is available under the GPLv3 license on GitHub at:
https://github.com/livingcomputermuseum/ContrAlto
Contributions are welcome!
9.0 Thanks and Acknowledgements
===============================
ContrAlto would not have been possible without the amazing preservation work of
the Computer History Museum.
Audio output and capture on Windows is provided using the NAudio libraries, see:
https://github.com/naudio/NAudio.
On Unix and OS X, display and keyboard/mouse input is provided through SDL, see:
https://www.libsdl.org/ and is accessed using the SDL2# wrapper, see:
https://github.com/flibitijibibo/SDL2-CS.
10.0 Change History
===================
V1.2
----
- First release supporting Unix / OS X
- Audio DAC for use with Smalltalk Music system implemented
- Initial implementation of Orbit rasterization device; Dover ROS is implemented
but not working properly.
- Added ability to load a configuration file at startup
V1.1
----
- A few minor performance tweaks, adding to a 10-15% speed increase.
- Switched back to targeting .NET 4.5.3 rather than 4.6; this works better under Mono
and avoids odd issues on Windows machines running pre-4.6 frameworks.
- Microcode disassembly improved slightly, annotated microcode source updated.
- Nova disassembler now handles BRI, DIR, EIR, DIRS instructions rather than treating
them all as TRAPs.
- Fixed serious bugs in memory state machine, BravoX now runs.
- Fixed minor bug in Constant ROM selection.
- Raw Ethernet packets sent as broadcasts (matching IFS encapsulation behavior)
V1.0
----
Initial release.

View File

@@ -1,11 +1,13 @@
Readme.txt for Contralto v1.1:
Readme.txt for Contralto v1.2:
1. Introduction and Overview
============================
ContrAlto aspires to be a faithful emulation of the Xerox Alto II series of
pioneering graphical workstations developed at Xerox PARC in 1973.
pioneering graphical workstations developed at Xerox PARC in 1973.
This document covers installation and use on Windows platforms. See
readme-mono.txt for instructions for Unix and OS X platforms.
1.1 What's Emulated
-------------------
@@ -18,6 +20,7 @@ ContrAlto currently emulates the following Alto hardware:
- Ethernet (encapsulated in either UDP datagrams or raw Ethernet frames
on the host machine)
- Standard Keyboard/Mouse/Video
- Audio DAC (used with the Smalltalk Music System)
1.2 What's Not
--------------
@@ -35,10 +38,10 @@ ContrAlto will run on any Windows PC running Windows Vista or later, with versio
on Windows Vista and later; if it is not installed on your computer it can be
obtained at https://www.microsoft.com/net.
As ContrAlto is a .NET application and has no Windows-specific dependencies,
it will also run under Mono (http://www.mono-project.com/) on Unix and MacOS,
however this usage has many rough edges at the moment -- please file bugs if
you find them (see Section 7 for details).
As ContrAlto is a .NET application it will also run under Mono
(http://www.mono-project.com/) on Unix and OS X -- please see the readme-mono.txt
file for details on using ContrAlto on these platforms as there are some
differences.
A three-button mouse is essential for using most Alto software. On most mice,
the mousewheel can be clicked to provide the third (middle) button. Laptops
@@ -304,8 +307,23 @@ The "Interlaced Display" checkbox attempts to simulate the Alto's original
interlaced display. Depending on your monitor and the speed of your computer
this may or may not work well.
4.4 DAC
-------
4.4 Alternate ("keyboard") Boots
The DAC tab provides options controlling the Audio DAC used by the Smalltalk
Music System. The "Enable Audio DAC" does what you'd expect -- it enables
or disables audio output (and audio capture). If this option is enabled,
the "DAC Options" fields become available.
"Enable DAC output capture" enables the capture of audio generated by the DAC
to be captured to one or more WAV files in the directory specified by the
"Output capture path" box. This box specifies a directory, not a file --
when the Alto starts generating audio a new WAV file will be created in this
directory. If the Alto is restarted or if ContrAlto exits, this file will
be closed. WAV files created by ContrAlto are 16-bit, mono at 13Khz.
4.5 Alternate ("keyboard") Boots
--------------------------------
The Alto allowed the specification of alternate boot addresses by holding down
@@ -475,10 +493,25 @@ Contributions are welcome!
ContrAlto would not have been possible without the amazing preservation work of
the Computer History Museum.
Audio output and capture on Windows is provided using the NAudio libraries, see:
https://github.com/naudio/NAudio.
On Unix and OS X, display and keyboard/mouse input is provided through SDL, see:
https://www.libsdl.org/ and is accessed using the SDL2# wrapper, see:
https://github.com/flibitijibibo/SDL2-CS.
10.0 Change History
===================
V1.2
----
- First release officially supporting Unix / OS X
- Audio DAC for use with Smalltalk Music system implemented
- Initial implementation of Orbit rasterization device; Dover ROS is implemented
but not working properly.
- Added ability to load a configuration file at startup
V1.1
----
- A few minor performance tweaks, adding to a 10-15% speed increase.

View File

@@ -107,7 +107,11 @@
<File Source="$(var.Contralto.TargetDir)\PcapDotNet.Core.dll"/>
<File Source="$(var.Contralto.TargetDir)\PcapDotNet.Core.Extensions.dll"/>
<File Source="$(var.Contralto.TargetDir)\PcapDotNet.Packets.dll"/>
<!-- SDL2-CS libs - at this time we don't use SDL2 on Windows but the SDL2-CS wrapper
still must be present. -->
<File Source="$(var.Contralto.TargetDir)\SDL2-CS.dll"/>
<!-- NAudio libs -->
<File Source="$(var.Contralto.TargetDir)\NAudio.dll"/>
</Component>