Extract Android dependencies from .netstandard libraries

This commit is contained in:
John Lewin 2018-12-15 08:53:46 -08:00
parent 6ea0de691c
commit 54c3dbb08e
12 changed files with 43 additions and 612 deletions

View file

@ -30,11 +30,13 @@ either expressed or implied, of the FreeBSD Project.
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using MatterHackers.Agg;
using MatterHackers.Agg.Image;
using MatterHackers.Agg.Platform;
using MatterHackers.Agg.UI;
using MatterHackers.MatterControl.PluginSystem;
using MatterHackers.MatterControl.PrinterControls.PrinterConnections;
namespace MatterHackers.MatterControl
{
@ -210,5 +212,31 @@ namespace MatterHackers.MatterControl
}
}
}
public void InitPluginFinder()
{
string searchPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
// Load plugins from all dlls in the startup directory
foreach (var file in Directory.GetFiles(searchPath, "*.dll"))
{
try
{
PluginFinder.LoadTypesFromAssembly(Assembly.LoadFile(file));
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("Error loading assembly: " + ex.Message);
}
}
}
public GuiWidget GetConnectDevicePage(object printer)
{
return new SetupStepComPortOne(printer as PrinterConfig);
}
// Primarily required for Android, return true on non-Android platforms
public bool HasPermissionToDevice(object printer) => true;
}
}

View file

@ -393,10 +393,7 @@ namespace MatterHackers.MatterControl
//Change download file to friendly file name
File.Move(updateFileName, friendlyFileName);
#if __ANDROID__
if (InstallUpdateFromMainActivity != null)
{
InstallUpdateFromMainActivity(this, null);
}
InstallUpdateFromMainActivity?.Invoke(this, null);
return true;
#else
int tries = 0;

View file

@ -13,6 +13,10 @@
<DefineConstants>TRACE;USE_OPENGL;IS_WINDOWS;IS_WINDOWS_FORMS</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(SolutionFileName)'=='MatterControlAndroid.sln'">
<DefineConstants>$(DefineConstants);__ANDROID__</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Community.CsharpSqlite\**" />
<Compile Remove="Launcher\**" />

View file

@ -170,15 +170,8 @@ namespace MatterHackers.MatterControl.ActionBar
listenForConnectFailed = true;
connectStartMs = UiThread.CurrentTimerMs;
#if __ANDROID__
if (!printer.Settings.GetValue<bool>(SettingsKey.enable_network_printing)
&& !FrostedSerialPort.HasPermissionToDevice())
{
// Opens the USB device permissions dialog which will call back into our UsbDevice broadcast receiver to connect
FrostedSerialPort.RequestPermissionToDevice(this.RunTroubleShooting);
}
else
#endif
&& AppContext.Platform.HasPermissionToDevice(printer))
{
printer.Connection.HaltConnectionThread();
printer.Connection.Connect();
@ -186,12 +179,6 @@ namespace MatterHackers.MatterControl.ActionBar
}
}
private void RunTroubleShooting()
{
DialogWindow.Show(
new SetupWizardTroubleshooting(printer));
}
private void Connection_Failed(object s, EventArgs e)
{
#if !__ANDROID__

View file

@ -893,7 +893,7 @@ namespace MatterHackers.MatterControl.PrinterCommunication
{
serialPort = portFactory.CreateAndOpen(serialPortName, Printer.Settings, baudRate, true);
#if __ANDROID__
ToggleHighLowHigh(serialPort);
ToggleHighLowHigh(serialPort);
#endif
// TODO: Review and reconsider the cases where this was required
// wait a bit of time to let the firmware start up

View file

@ -181,14 +181,10 @@ namespace MatterHackers.MatterControl.PrinterControls.PrinterConnections
return;
}
#if __ANDROID__
UiThread.RunOnIdle(() => DialogWindow.ChangeToPage(new AndroidConnectDevicePage(printer)));
#else
UiThread.RunOnIdle(() =>
{
DialogWindow.ChangeToPage(new SetupStepComPortOne(printer));
DialogWindow.ChangeToPage(AppContext.Platform.GetConnectDevicePage(printer) as DialogPage);
});
#endif
}
}
});

View file

@ -1,208 +0,0 @@
/*
Copyright (c) 2016, Kevin Pope, John Lewin
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation are those
of the authors and should not be interpreted as representing official policies,
either expressed or implied, of the FreeBSD Project.
*/
using System;
using MatterHackers.Agg;
using MatterHackers.Agg.UI;
using MatterHackers.Localizations;
using MatterHackers.MatterControl.CustomWidgets;
using MatterHackers.MatterControl.PrinterCommunication;
namespace MatterHackers.MatterControl
{
public class AndroidConnectDevicePage : DialogPage
{
private TextWidget generalError;
private GuiWidget connectButton;
private GuiWidget skipButton;
private GuiWidget nextButton;
private GuiWidget retryButton;
private GuiWidget troubleshootButton;
private TextWidget skipMessage;
private FlowLayoutWidget retryButtonContainer;
private FlowLayoutWidget connectButtonContainer;
private PrinterConfig printer;
public AndroidConnectDevicePage(PrinterConfig printer)
{
this.printer = printer;
var printerNameLabel = new TextWidget("Connect Your Device".Localize() + ":", 0, 0, labelFontSize)
{
TextColor = theme.TextColor,
Margin = new BorderDouble(bottom: 10)
};
contentRow.AddChild(printerNameLabel);
contentRow.AddChild(new TextWidget("Instructions".Localize() + ":", 0, 0, 12,textColor:theme.TextColor));
contentRow.AddChild(new TextWidget("1. Power on your 3D Printer.".Localize(), 0, 0, 12,textColor:theme.TextColor));
contentRow.AddChild(new TextWidget("2. Attach your 3D Printer via USB.".Localize(), 0, 0, 12,textColor:theme.TextColor));
contentRow.AddChild(new TextWidget("3. Press 'Connect'.".Localize(), 0, 0, 12,textColor:theme.TextColor));
//Add inputs to main container
connectButtonContainer = new FlowLayoutWidget()
{
HAnchor = HAnchor.Stretch,
Margin = new BorderDouble(0, 6)
};
connectButton = theme.CreateLightDialogButton("Connect".Localize());
connectButton.Margin = new BorderDouble(0,0,10,0);
connectButton.Click += ConnectButton_Click;
skipButton = theme.CreateLightDialogButton("Skip".Localize());
skipButton.Click += NextButton_Click;
connectButtonContainer.AddChild(connectButton);
connectButtonContainer.AddChild(skipButton);
connectButtonContainer.AddChild(new HorizontalSpacer());
contentRow.AddChild(connectButtonContainer);
skipMessage = new TextWidget("(Press 'Skip' to setup connection later)".Localize(), 0, 0, 10, textColor: theme.TextColor);
contentRow.AddChild(skipMessage);
generalError = new TextWidget("", 0, 0, errorFontSize)
{
TextColor = theme.PrimaryAccentColor,
HAnchor = HAnchor.Stretch,
Visible = false,
Margin = new BorderDouble(top: 20),
};
contentRow.AddChild(generalError);
//Construct buttons
retryButton = theme.CreateLightDialogButton("Retry".Localize());
retryButton.Click += ConnectButton_Click;
retryButton.Margin = new BorderDouble(0,0,10,0);
//Construct buttons
troubleshootButton = theme.CreateLightDialogButton("Troubleshoot".Localize());
troubleshootButton.Click += (s, e) => UiThread.RunOnIdle(() =>
{
DialogWindow.ChangeToPage(
new SetupWizardTroubleshooting(printer));
});
retryButtonContainer = new FlowLayoutWidget()
{
HAnchor = HAnchor.Stretch,
Margin = new BorderDouble(0, 6),
Visible = false
};
retryButtonContainer.AddChild(retryButton);
retryButtonContainer.AddChild(troubleshootButton);
retryButtonContainer.AddChild(new HorizontalSpacer());
contentRow.AddChild(retryButtonContainer);
//Construct buttons
nextButton = theme.CreateDialogButton("Continue".Localize());
nextButton.Click += NextButton_Click;
nextButton.Visible = false;
GuiWidget hSpacer = new GuiWidget();
hSpacer.HAnchor = HAnchor.Stretch;
this.AddPageAction(nextButton);
// Register listeners
printer.Connection.CommunicationStateChanged += Connection_CommunicationStateChanged;
updateControls(true);
}
void ConnectButton_Click(object sender, EventArgs mouseEvent)
{
printer.Connection.Connect();
}
void NextButton_Click(object sender, EventArgs mouseEvent)
{
this.generalError.Text = "Please wait...";
this.generalError.Visible = true;
nextButton.Visible = false;
UiThread.RunOnIdle(this.DialogWindow.Close);
}
private void Connection_CommunicationStateChanged(object sender, EventArgs args)
{
UiThread.RunOnIdle(() => updateControls(false));
}
private void updateControls(bool firstLoad)
{
connectButton.Visible = false;
skipMessage.Visible = false;
generalError.Visible = false;
nextButton.Visible = false;
connectButtonContainer.Visible = false;
retryButtonContainer.Visible = false;
if (printer.Connection.IsConnected)
{
generalError.Text = "{0}!".FormatWith ("Connection succeeded".Localize ());
generalError.Visible = true;
nextButton.Visible = true;
}
else if (firstLoad || printer.Connection.CommunicationState == CommunicationStates.Disconnected)
{
generalError.Text = "";
connectButton.Visible = true;
connectButtonContainer.Visible = true;
}
else if (printer.Connection.CommunicationState == CommunicationStates.AttemptingToConnect)
{
generalError.Text = "{0}...".FormatWith("Attempting to connect".Localize());
generalError.Visible = true;
}
else
{
generalError.Text = "Uh-oh! Could not connect to printer.".Localize();
generalError.Visible = true;
nextButton.Visible = false;
retryButtonContainer.Visible = true;
}
this.Invalidate();
}
public override void OnClosed(EventArgs e)
{
// Unregister listeners
printer.Connection.CommunicationStateChanged -= Connection_CommunicationStateChanged;
base.OnClosed(e);
}
}
}

View file

@ -1,57 +0,0 @@
/*
Copyright (c) 2017, Kevin Pope, John Lewin
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation are those
of the authors and should not be interpreted as representing official policies,
either expressed or implied, of the FreeBSD Project.
*/
using MatterHackers.Agg;
using MatterHackers.Agg.UI;
using MatterHackers.Localizations;
namespace MatterHackers.MatterControl
{
public class NetworkTroubleshooting : DialogPage
{
public NetworkTroubleshooting()
{
contentRow.AddChild(
new TextWidget(
"MatterControl was unable to connect to the Internet. Please check your Wifi connection and try again".Localize() + "...",
textColor: theme.TextColor));
var configureButton = theme.CreateDialogButton("Configure Wifi".Localize());
configureButton.Margin = new BorderDouble(0, 0, 10, 0);
configureButton.Click += (s, e) =>
{
MatterControl.AppContext.Platform.ConfigureWifi();
UiThread.RunOnIdle(DialogWindow.Close, 1);
};
//Add buttons to buttonContainer
AddPageAction(configureButton);
}
}
}

View file

@ -1,320 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using MatterHackers.Agg;
using MatterHackers.Agg.Platform;
using MatterHackers.Agg.UI;
using MatterHackers.Localizations;
using MatterHackers.MatterControl.ConfigurationPage.PrintLeveling;
using MatterHackers.MatterControl.DataStorage;
using MatterHackers.MatterControl.CustomWidgets;
using MatterHackers.MatterControl.PrinterCommunication;
using MatterHackers.SerialPortCommunication.FrostedSerial;
#if __ANDROID__
using Com.Hoho.Android.Usbserial.Driver;
using Android.Hardware.Usb;
using Android.Content;
#endif
namespace MatterHackers.MatterControl
{
public class SetupWizardTroubleshooting : DialogPage
{
private GuiWidget nextButton;
private CriteriaRow connectToPrinterRow;
// Used in Android
private System.Threading.Timer checkForPermissionTimer;
private PrinterConfig printer;
#if __ANDROID__
private static UsbManager usbManager
{
get { return (UsbManager) Android.App.Application.Context.ApplicationContext.GetSystemService(Context.UsbService); }
}
#endif
public SetupWizardTroubleshooting(PrinterConfig printer)
{
this.WindowTitle = "Troubleshooting".Localize();
this.printer = printer;
RefreshStatus();
nextButton = theme.CreateDialogButton("Continue".Localize());
nextButton.Click += (s, e) => UiThread.RunOnIdle(this.DialogWindow.Close);
nextButton.Visible = false;
this.AddPageAction(nextButton);
// Register listeners
printer.Connection.CommunicationStateChanged += Connection_CommunicationStateChanged;
}
public void Connection_CommunicationStateChanged(object test, EventArgs args)
{
if (printer.Connection.CommunicationState == CommunicationStates.Connected && connectToPrinterRow != null)
{
connectToPrinterRow.SetSuccessful();
nextButton.Visible = true;
}
}
protected override void OnCancel(out bool abortCancel)
{
abortCancel = true;
UiThread.RunOnIdle(() =>
{
this.DialogWindow.ChangeToPage(new AndroidConnectDevicePage(printer));
});
}
public override void OnClosed(EventArgs e)
{
// Unregister listeners
printer.Connection.CommunicationStateChanged -= Connection_CommunicationStateChanged;
if (checkForPermissionTimer != null)
{
checkForPermissionTimer.Dispose();
}
base.OnClosed(e);
}
private void RefreshStatus()
{
CriteriaRow.ResetAll();
// Clear the main container
contentRow.CloseAllChildren();
// Regen and refresh the troubleshooting criteria
var printerNameLabel = new TextWidget(string.Format("{0}:", "Connection Troubleshooting".Localize()), 0, 0, labelFontSize)
{
TextColor = theme.TextColor,
Margin = new BorderDouble(bottom: 10)
};
#if __ANDROID__
IUsbSerialPort serialPort = FrostedSerialPort.LoadSerialDriver(null);
#if ANDROID7
// Filter out the built-in 002 device and select the first item from the list
// On the T7 Android device, there is a non-printer device always registered at usb/002/002 that must be ignored
UsbDevice usbPrintDevice = usbManager.DeviceList.Values.Where(d => d.DeviceName != "/dev/bus/usb/002/002").FirstOrDefault();
#else
UsbDevice usbPrintDevice = usbManager.DeviceList.Values.FirstOrDefault();
#endif
UsbStatus usbStatus = new UsbStatus () {
IsDriverLoadable = (serialPort != null),
HasUsbDevice = true,
HasUsbPermission = false,
AnyUsbDeviceExists = usbPrintDevice != null
};
if (!usbStatus.IsDriverLoadable)
{
usbStatus.HasUsbDevice = usbPrintDevice != null;
if (usbStatus.HasUsbDevice) {
// TODO: Testing specifically for UsbClass.Comm seems fragile but no better alternative exists without more research
usbStatus.UsbDetails = new UsbDeviceDetails () {
ProductID = usbPrintDevice.ProductId,
VendorID = usbPrintDevice.VendorId,
DriverClass = usbManager.DeviceList.Values.First ().DeviceClass == Android.Hardware.Usb.UsbClass.Comm ? "cdcDriverType" : "ftdiDriverType"
};
usbStatus.Summary = string.Format ("No USB device definition found. Click the 'Fix' button to add an override for your device ", usbStatus.UsbDetails.VendorID, usbStatus.UsbDetails.ProductID);
}
}
usbStatus.HasUsbPermission = usbStatus.IsDriverLoadable && FrostedSerialPort.HasPermissionToDevice(serialPort);
contentRow.AddChild(printerNameLabel);
contentRow.AddChild(new CriteriaRow(
"USB Connection",
"Retry",
"No USB device found. Check and reseat cables and try again",
usbStatus.AnyUsbDeviceExists,
() => UiThread.RunOnIdle(RefreshStatus),
theme));
contentRow.AddChild(new CriteriaRow(
"USB Driver",
"Fix",
usbStatus.Summary,
usbStatus.IsDriverLoadable,
() =>
{
string overridePath = Path.Combine(ApplicationDataStorage.ApplicationUserDataPath, "data", "usboverride.local");
UsbDeviceDetails usbDetails = usbStatus.UsbDetails;
File.AppendAllText(overridePath, string.Format("{0},{1},{2}\r\n", usbDetails.VendorID, usbDetails.ProductID, usbDetails.DriverClass));
UiThread.RunOnIdle(() => RefreshStatus());
},
theme));
contentRow.AddChild(new CriteriaRow(
"USB Permission",
"Request Permission",
"Click the 'Request Permission' button to gain Android access rights",
usbStatus.HasUsbPermission,
() =>
{
if (checkForPermissionTimer == null)
{
checkForPermissionTimer = new System.Threading.Timer((state) =>
{
if (FrostedSerialPort.HasPermissionToDevice(serialPort))
{
UiThread.RunOnIdle(this.RefreshStatus);
checkForPermissionTimer.Dispose();
}
}, null, 200, 200);
}
FrostedSerialPort.RequestPermissionToDevice(serialPort);
},
theme));
#endif
connectToPrinterRow = new CriteriaRow(
"Connect to Printer".Localize(),
"Connect".Localize(),
"Click the 'Connect' button to retry the original connection attempt".Localize(),
false,
() => printer.Connection.Connect(),
theme);
contentRow.AddChild(connectToPrinterRow);
if (CriteriaRow.ActiveErrorItem != null) {
var errorText = new FlowLayoutWidget () {
Padding = new BorderDouble (0, 15)
};
errorText.AddChild(
new TextWidget(CriteriaRow.ActiveErrorItem.ErrorText)
{
TextColor = theme.PrimaryAccentColor
});
contentRow.AddChild(errorText);
}
}
private class CriteriaRow : FlowLayoutWidget
{
public static CriteriaRow ActiveErrorItem { get; private set; }
public string ErrorText { get; private set; }
private static bool stillSuccessful = true;
private static int criteriaCount = 0;
private static Color disabledTextColor = new Color(0.35, 0.35, 0.35);
private static Color disabledBackColor = new Color(0.22, 0.22, 0.22);
private static Color toggleColor = new Color(Color.Gray.red + 2, Color.Gray.green + 2, Color.Gray.blue + 2);
public CriteriaRow (string itemText, string fixitText, string errorText, bool succeeded, Action fixAction, ThemeConfig theme)
: base(FlowDirection.LeftToRight)
{
HAnchor = HAnchor.Stretch;
VAnchor = VAnchor.Absolute;
ErrorText = errorText;
base.Height = 40;
base.AddChild(new TextWidget (string.Format(" {0}. {1}", criteriaCount + 1, itemText)){
TextColor = stillSuccessful ? Color.White : disabledTextColor,
VAnchor = VAnchor.Center
});
if(stillSuccessful && !succeeded)
{
ActiveErrorItem = this;
}
base.AddChild(new HorizontalSpacer());
if(stillSuccessful) {
if(succeeded)
{
// Add checkmark image
AddSuccessIcon();
} else {
// Add Fix button
var button = theme.CreateDialogButton(fixitText);
button.VAnchor = VAnchor.Center;
button.Padding = new BorderDouble(3, 8);
button.Click += (s, e) => fixAction?.Invoke();
base.AddChild(button);
}
}
if(stillSuccessful)
{
this.BackgroundColor = (criteriaCount % 2 == 0) ? Color.Gray : toggleColor;
}
else
{
this.BackgroundColor = disabledBackColor;
}
stillSuccessful &= succeeded;
criteriaCount++;
}
public void SetSuccessful()
{
this.RemoveChild (this.Children.Last ());
ActiveErrorItem = null;
AddSuccessIcon();
}
public static void ResetAll()
{
criteriaCount = 0;
stillSuccessful = true;
ActiveErrorItem = null;
}
private void AddSuccessIcon()
{
base.AddChild (new ImageWidget (AggContext.StaticData.LoadImage (Path.Combine ("Icons", "426.png"))) {
VAnchor = VAnchor.Center
});
}
}
private class UsbStatus
{
public bool HasUsbDevice { get; set; }
public bool IsDriverLoadable { get; set; }
public string Summary { get; set; }
public bool HasUsbPermission { get; set; }
public bool AnyUsbDeviceExists { get ; set; }
public UsbDeviceDetails UsbDetails { get; set; }
}
private class UsbDeviceDetails
{
public int VendorID { get; set; }
public int ProductID { get; set; }
public string DriverClass { get; set; }
}
}
}

View file

@ -41,9 +41,12 @@ namespace MatterHackers.MatterControl
void ConfigureWifi();
bool CameraInUseByExternalProcess { get; set; }
bool IsNetworkConnected();
void InitPluginFinder();
GuiWidget GetConnectDevicePage(object printer);
void FindAndInstantiatePlugins(SystemWindow systemWindow);
void ProcessCommandline();
void PlatformInit(Action<string> reporter);
void GenerateLocalizationValidationFile();
bool HasPermissionToDevice(object printer);
}
}

View file

@ -128,8 +128,9 @@ namespace MatterHackers.MatterControl
// Init platformFeaturesProvider before ShowAsSystemWindow
string platformFeaturesProvider = "MatterHackers.MatterControl.WindowsPlatformsFeatures, MatterControl.Winforms";
MatterHackers.MatterControl.AppContext.Platform = AggContext.CreateInstanceFrom<INativePlatformFeatures>(platformFeaturesProvider);
MatterHackers.MatterControl.AppContext.Platform.ProcessCommandline();
AppContext.Platform = AggContext.CreateInstanceFrom<INativePlatformFeatures>(platformFeaturesProvider);
AppContext.Platform.InitPluginFinder();
AppContext.Platform.ProcessCommandline();
config.Bind("MatterControl", MatterHackers.MatterControl.AppContext.Options);

@ -1 +1 @@
Subproject commit c583dd6d0a62f478e456a25552935370446ab099
Subproject commit 4261fd059aeb45648eb24f53e6b784641deded41