Add minidump generation, more error handling and logging

pull/7/head
LeapwardKoex 2024-02-13 22:40:12 +13:00
parent 6f364833a8
commit 31f97060b8
8 changed files with 266 additions and 89 deletions

View File

@ -1,5 +1,5 @@
using System.Configuration; using System.Diagnostics;
using System.Data; using System.IO;
using System.Windows; using System.Windows;
namespace WpfMaiTouchEmulator; namespace WpfMaiTouchEmulator;
@ -8,5 +8,38 @@ namespace WpfMaiTouchEmulator;
/// </summary> /// </summary>
public partial class App : Application public partial class App : Application
{ {
public App()
{
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
DispatcherUnhandledException += App_DispatcherUnhandledException;
Logger.CleanupOldLogFiles();
}
private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
CreateDump(e.Exception);
e.Handled = true;
}
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
CreateDump(e.ExceptionObject as Exception);
}
private void CreateDump(Exception exception)
{
var dumpFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "CrashDump.dmp");
using (var fs = new FileStream(dumpFilePath, FileMode.Create))
{
var process = Process.GetCurrentProcess();
DumpCreator.MiniDumpWriteDump(process.Handle, (uint)process.Id, fs.SafeFileHandle, DumpCreator.Typ.MiniDumpNormal, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
}
Logger.Fatal("App encountered a fatal exception", exception);
MessageBox.Show($"A uncaught exception was thrown: {exception.Message}. \n\n {exception.StackTrace}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
Current.Shutdown(1);
}
} }

20
DumpCreator.cs 100644
View File

@ -0,0 +1,20 @@
using System;
using System.Runtime.InteropServices;
using System.IO;
using System.Diagnostics;
class DumpCreator
{
[Flags]
public enum Typ : uint
{
// Add the MiniDump flags you need, for example:
MiniDumpNormal = 0x00000000,
MiniDumpWithDataSegs = 0x00000001,
// etc.
}
[DllImport("DbgHelp.dll")]
public static extern bool MiniDumpWriteDump(IntPtr hProcess, uint ProcessId, SafeHandle hFile, Typ DumpType,
IntPtr ExceptionParam, IntPtr UserStreamParam, IntPtr CallbackParam);
}

91
Logger.cs 100644
View File

@ -0,0 +1,91 @@
using System;
using System.IO;
using System.Text;
public static class Logger
{
private static readonly object lockObj = new();
private static string? logFilePath;
private static string GetLogFilePath()
{
if (logFilePath == null)
{
var fileName = $"app_{DateTime.Now:yyyy-MM-dd}.log";
logFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);
}
return logFilePath;
}
public static void CleanupOldLogFiles()
{
var directory = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory);
var oldFiles = directory.GetFiles("app_*.log")
.Where(f => f.CreationTime < DateTime.Now.AddDays(-7))
.ToList();
foreach (var file in oldFiles)
{
try
{
file.Delete();
}
catch (Exception ex)
{
Error("Failed to delete log file", ex);
}
}
}
public static void Info(string message)
{
Log("INFO", message);
}
public static void Warn(string message)
{
Log("WARN", message);
}
public static void Error(string message, Exception? ex = null)
{
var logMessage = new StringBuilder(message);
if (ex != null)
{
logMessage.AppendLine(); // Ensure the exception starts on a new line
logMessage.AppendLine($"Exception: {ex.Message}");
logMessage.AppendLine($"StackTrace: {ex.StackTrace}");
}
Log("ERROR", logMessage.ToString());
}
public static void Fatal(string message, Exception? ex = null)
{
var logMessage = new StringBuilder(message);
if (ex != null)
{
logMessage.AppendLine(); // Ensure the exception starts on a new line
logMessage.AppendLine($"Exception: {ex.Message}");
logMessage.AppendLine($"StackTrace: {ex.StackTrace}");
}
Log("FATAL", logMessage.ToString());
}
private static void Log(string level, string message)
{
try
{
lock (lockObj)
{
using var sw = new StreamWriter(GetLogFilePath(), true, Encoding.UTF8);
sw.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [{level}] {message}");
}
}
catch
{
}
}
}

View File

@ -2,54 +2,52 @@
using System.Windows; using System.Windows;
namespace WpfMaiTouchEmulator; namespace WpfMaiTouchEmulator;
internal class MaiTouchComConnector internal class MaiTouchComConnector(MaiTouchSensorButtonStateManager buttonState)
{ {
private static SerialPort? serialPort; private static SerialPort? serialPort;
private bool isActiveMode; private bool isActiveMode;
private bool _connected; private bool _connected;
private bool _shouldReconnect = true; private bool _shouldReconnect = true;
private readonly MaiTouchSensorButtonStateManager _buttonState; private readonly MaiTouchSensorButtonStateManager _buttonState = buttonState;
public Action<string> OnConnectStatusChange public Action<string>? OnConnectStatusChange
{ {
get; get;
internal set; internal set;
} }
public Action OnConnectError public Action? OnConnectError
{ {
get; get;
internal set; internal set;
} }
public Action<string> OnDataSent public Action<string>? OnDataSent
{ {
get; get;
internal set; internal set;
} }
public Action<string> OnDataRecieved public Action<string>? OnDataRecieved
{ {
get; get;
internal set; internal set;
} }
public MaiTouchComConnector(MaiTouchSensorButtonStateManager buttonState)
{
_buttonState = buttonState;
}
public async Task StartTouchSensorPolling() public async Task StartTouchSensorPolling()
{ {
if (!_connected && _shouldReconnect) if (!_connected && _shouldReconnect)
{ {
Logger.Info("Trying to connect to COM port...");
var virtualPort = "COM23"; // Adjust as needed var virtualPort = "COM23"; // Adjust as needed
try try
{ {
OnConnectStatusChange("Conecting..."); OnConnectStatusChange?.Invoke("Conecting...");
serialPort = new SerialPort(virtualPort, 9600, Parity.None, 8, StopBits.One); serialPort = new SerialPort(virtualPort, 9600, Parity.None, 8, StopBits.One)
serialPort.WriteTimeout = 100; {
WriteTimeout = 100
};
serialPort.DataReceived += SerialPort_DataReceived; serialPort.DataReceived += SerialPort_DataReceived;
serialPort.Open(); serialPort.Open();
Console.WriteLine("Serial port opened successfully."); Logger.Info("Serial port opened successfully.");
OnConnectStatusChange("Connected to port"); OnConnectStatusChange?.Invoke("Connected to port");
_connected = true; _connected = true;
while (true) while (true)
@ -69,7 +67,7 @@ internal class MaiTouchComConnector
catch (TimeoutException) { } catch (TimeoutException) { }
catch (Exception ex) catch (Exception ex)
{ {
OnConnectError(); OnConnectError?.Invoke();
Application.Current.Dispatcher.Invoke(() => Application.Current.Dispatcher.Invoke(() =>
{ {
MessageBox.Show(ex.Message, "Error connecting to COM port", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(ex.Message, "Error connecting to COM port", MessageBoxButton.OK, MessageBoxImage.Error);
@ -78,10 +76,13 @@ internal class MaiTouchComConnector
} }
finally finally
{ {
Logger.Info("Disconnecting from COM port");
_connected = false; _connected = false;
OnConnectStatusChange("Not Connected"); OnConnectStatusChange?.Invoke("Not Connected");
if (serialPort?.IsOpen == true) if (serialPort?.IsOpen == true)
{ {
serialPort.DiscardInBuffer();
serialPort.DiscardOutBuffer();
serialPort.Close(); serialPort.Close();
} }
} }
@ -90,62 +91,71 @@ internal class MaiTouchComConnector
public async Task Disconnect() public async Task Disconnect()
{ {
Logger.Info("Disconnecting from COM port");
_shouldReconnect = false; _shouldReconnect = false;
_connected = false; _connected = false;
try try
{ {
serialPort.DtrEnable = false; if (serialPort != null)
serialPort.RtsEnable = false;
serialPort.DataReceived -= SerialPort_DataReceived;
await Task.Delay(200);
if (serialPort.IsOpen == true)
{ {
serialPort.DiscardInBuffer(); serialPort.DtrEnable = false;
serialPort.DiscardOutBuffer(); serialPort.RtsEnable = false;
serialPort.Close(); serialPort.DataReceived -= SerialPort_DataReceived;
await Task.Delay(200);
if (serialPort.IsOpen == true)
{
serialPort.DiscardInBuffer();
serialPort.DiscardOutBuffer();
serialPort.Close();
}
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Error("Error whilst disconnecting from COM port", ex);
MessageBox.Show(ex.Message); MessageBox.Show(ex.Message);
} }
} }
void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{ {
var recievedData = serialPort.ReadExisting(); var recievedData = serialPort?.ReadExisting();
var commands = recievedData.Split(new[] { '}' }, StringSplitOptions.RemoveEmptyEntries); var commands = recievedData?.Split(new[] { '}' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var command in commands) if (commands != null)
{ {
var cleanedCommand = command.TrimStart('{'); foreach (var command in commands)
Console.WriteLine($"Received data: {cleanedCommand}"); {
OnDataRecieved(cleanedCommand); var cleanedCommand = command.TrimStart('{');
Logger.Info($"Received serial data: {cleanedCommand}");
OnDataRecieved?.Invoke(cleanedCommand);
if (cleanedCommand == "STAT") if (cleanedCommand == "STAT")
{ {
isActiveMode = true; isActiveMode = true;
} }
else if (cleanedCommand == "RSET") else if (cleanedCommand == "RSET")
{ {
} }
else if (cleanedCommand == "HALT") else if (cleanedCommand == "HALT")
{ {
isActiveMode = false; isActiveMode = false;
} }
else if (cleanedCommand[2] == 'r' || cleanedCommand[2] == 'k') else if (cleanedCommand[2] == 'r' || cleanedCommand[2] == 'k')
{ {
var leftOrRight = cleanedCommand[0]; var leftOrRight = cleanedCommand[0];
var sensor = cleanedCommand[1]; var sensor = cleanedCommand[1];
var ratio = cleanedCommand[3]; var ratio = cleanedCommand[3];
var newString = $"({leftOrRight}{sensor}{cleanedCommand[2]}{ratio})"; var newString = $"({leftOrRight}{sensor}{cleanedCommand[2]}{ratio})";
serialPort.Write(newString); serialPort?.Write(newString);
OnDataSent(newString); OnDataSent?.Invoke(newString);
} }
else else
{ {
Console.WriteLine(cleanedCommand); Logger.Warn($"Unhandled serial data command {cleanedCommand}");
}
} }
} }
} }

View File

@ -47,12 +47,14 @@ public partial class MainWindow : Window
if (Properties.Settings.Default.FirstOpen) if (Properties.Settings.Default.FirstOpen)
{ {
Logger.Info("First open occurred");
MessageBox.Show("Please remove any COM devices using the COM3 port before installing the virtual COM port. In Device Manager click \"View\" then enabled \"Show hidden devices\" and uninstall any devices that are using the COM3 port.\n\nAfter ensuring COM3 is free please use the install COM port button in the app to register the app.\n\nThe app needs to connect to the port prior to Sinmai.exe being opened.", "First time setup", MessageBoxButton.OK, MessageBoxImage.Information); MessageBox.Show("Please remove any COM devices using the COM3 port before installing the virtual COM port. In Device Manager click \"View\" then enabled \"Show hidden devices\" and uninstall any devices that are using the COM3 port.\n\nAfter ensuring COM3 is free please use the install COM port button in the app to register the app.\n\nThe app needs to connect to the port prior to Sinmai.exe being opened.", "First time setup", MessageBoxButton.OK, MessageBoxImage.Information);
Properties.Settings.Default.FirstOpen = false; Properties.Settings.Default.FirstOpen = false;
Properties.Settings.Default.Save(); Properties.Settings.Default.Save();
} }
Loaded += (s, e) => { Loaded += (s, e) => {
Logger.Info("Main window loaded, creating touch panel");
_touchPanel = new TouchPanel(); _touchPanel = new TouchPanel();
_touchPanel.onTouch = (value) => { buttonState.PressButton(value); }; _touchPanel.onTouch = (value) => { buttonState.PressButton(value); };
_touchPanel.onRelease = (value) => { buttonState.ReleaseButton(value); }; _touchPanel.onRelease = (value) => { buttonState.ReleaseButton(value); };
@ -90,6 +92,7 @@ public partial class MainWindow : Window
var processes = Process.GetProcessesByName("Sinmai"); var processes = Process.GetProcessesByName("Sinmai");
if (processes.Length > 0) if (processes.Length > 0)
{ {
Logger.Info("Found sinmai process to exit alongside with");
sinamiProcess = processes[0]; sinamiProcess = processes[0];
} }
else else
@ -98,10 +101,13 @@ public partial class MainWindow : Window
} }
} }
await sinamiProcess.WaitForExitAsync(); await sinamiProcess.WaitForExitAsync();
Logger.Info("Sinmai exited");
var dataContext = (MainWindowViewModel)DataContext; var dataContext = (MainWindowViewModel)DataContext;
if (dataContext.IsExitWithSinmaiEnabled) if (dataContext.IsExitWithSinmaiEnabled)
{ {
Logger.Info("Disconnecting from COM port before shutting down");
await connector.Disconnect(); await connector.Disconnect();
Logger.Info("Shutting down...");
Application.Current.Shutdown(); Application.Current.Shutdown();
} }
} }
@ -178,6 +184,8 @@ public partial class MainWindow : Window
private async void buttonInstallComPort_Click(object sender, RoutedEventArgs e) private async void buttonInstallComPort_Click(object sender, RoutedEventArgs e)
{ {
throw new Exception("Test exception for crash dump generation");
await comPortManager.InstallComPort(); await comPortManager.InstallComPort();
} }

View File

@ -11,10 +11,10 @@ namespace WpfMaiTouchEmulator;
/// </summary> /// </summary>
public partial class TouchPanel : Window public partial class TouchPanel : Window
{ {
internal Action<TouchValue> onTouch; internal Action<TouchValue>? onTouch;
internal Action<TouchValue> onRelease; internal Action<TouchValue>? onRelease;
private readonly Dictionary<int, System.Windows.Controls.Image> activeTouches = []; private readonly Dictionary<int, Image> activeTouches = [];
private readonly TouchPanelPositionManager _positionManager; private readonly TouchPanelPositionManager _positionManager;
private List<Image> buttons = []; private List<Image> buttons = [];
@ -44,10 +44,10 @@ public partial class TouchPanel : Window
{ {
while (true) while (true)
{ {
if (activeTouches.Any() && !TouchesOver.Any()) if (activeTouches.Count != 0 && !TouchesOver.Any())
{ {
await Task.Delay(100); await Task.Delay(100);
if (activeTouches.Any() && !TouchesOver.Any()) if (activeTouches.Count != 0 && !TouchesOver.Any())
{ {
DeselectAllItems(); DeselectAllItems();
} }
@ -64,8 +64,11 @@ public partial class TouchPanel : Window
public void PositionTouchPanel() public void PositionTouchPanel()
{ {
var position = _positionManager.GetSinMaiWindowPosition(); var position = _positionManager.GetSinMaiWindowPosition();
if (position != null) if (position != null &&
(Top != position.Value.Top || Left != position.Value.Left || Width != position.Value.Width || Height != position.Value.Height)
)
{ {
Logger.Info("Touch panel not over sinmai window, repositioning");
Top = position.Value.Top; Top = position.Value.Top;
Left = position.Value.Left; Left = position.Value.Left;
Width = position.Value.Width; Width = position.Value.Width;
@ -103,7 +106,7 @@ public partial class TouchPanel : Window
{ {
// Highlight the element and add it to the active touches tracking. // Highlight the element and add it to the active touches tracking.
HighlightElement(element, true); HighlightElement(element, true);
onTouch((TouchValue)element.Tag); onTouch?.Invoke((TouchValue)element.Tag);
activeTouches[e.TouchDevice.Id] = element; activeTouches[e.TouchDevice.Id] = element;
} }
e.Handled = true; e.Handled = true;
@ -125,7 +128,7 @@ public partial class TouchPanel : Window
HighlightElement(previousElement, false); HighlightElement(previousElement, false);
Application.Current.Dispatcher.Invoke(() => Application.Current.Dispatcher.Invoke(() =>
{ {
onRelease((TouchValue)previousElement.Tag); onRelease?.Invoke((TouchValue)previousElement.Tag);
}); });
}); });
@ -133,7 +136,7 @@ public partial class TouchPanel : Window
// Highlight the new element and update the tracking. // Highlight the new element and update the tracking.
HighlightElement(newElement, true); HighlightElement(newElement, true);
onTouch((TouchValue)newElement.Tag); onTouch?.Invoke((TouchValue)newElement.Tag);
activeTouches[e.TouchDevice.Id] = newElement; activeTouches[e.TouchDevice.Id] = newElement;
} }
@ -146,29 +149,20 @@ public partial class TouchPanel : Window
if (activeTouches.TryGetValue(e.TouchDevice.Id, out var element)) if (activeTouches.TryGetValue(e.TouchDevice.Id, out var element))
{ {
HighlightElement(element, false); HighlightElement(element, false);
onRelease((TouchValue)element.Tag); onRelease?.Invoke((TouchValue)element.Tag);
activeTouches.Remove(e.TouchDevice.Id); activeTouches.Remove(e.TouchDevice.Id);
} }
e.Handled = true; e.Handled = true;
} }
private bool IsTouchInsideWindow(Point touchPoint)
{
// Define the window's bounds
var windowBounds = new Rect(0, 0, ActualWidth, ActualHeight);
// Check if the touch point is within the window's bounds
return windowBounds.Contains(touchPoint);
}
private void DeselectAllItems() private void DeselectAllItems()
{ {
// Logic to deselect all items or the last touched item // Logic to deselect all items or the last touched item
foreach (var element in activeTouches.Values) foreach (var element in activeTouches.Values)
{ {
HighlightElement(element, false); HighlightElement(element, false);
onRelease((TouchValue)element.Tag); onRelease?.Invoke((TouchValue)element.Tag);
} }
activeTouches.Clear(); activeTouches.Clear();
} }
@ -181,7 +175,7 @@ public partial class TouchPanel : Window
}); });
} }
private void HighlightElement(System.Windows.Controls.Image element, bool highlight) private void HighlightElement(Image element, bool highlight)
{ {
if (Properties.Settings.Default.IsDebugEnabled) if (Properties.Settings.Default.IsDebugEnabled)
{ {

View File

@ -23,20 +23,28 @@ class TouchPanelPositionManager
public Rect? GetSinMaiWindowPosition() public Rect? GetSinMaiWindowPosition()
{ {
var hWnd = FindWindow(null, "Sinmai"); try
if (hWnd != IntPtr.Zero)
{ {
RECT rect; var hWnd = FindWindow(null, "Sinmai");
if (GetWindowRect(hWnd, out rect)) if (hWnd != IntPtr.Zero)
{ {
// Calculate the desired size and position based on the other application's window RECT rect;
var width = Convert.ToInt32((rect.Right - rect.Left)); if (GetWindowRect(hWnd, out rect))
var height = width; {
var left = rect.Left + ((rect.Right - rect.Left) - width) / 2; // Center horizontally // Calculate the desired size and position based on the other application's window
var top = rect.Bottom - height; var width = rect.Right - rect.Left;
return new Rect(left, top, width, height); var height = width;
var left = rect.Left + ((rect.Right - rect.Left) - width) / 2; // Center horizontally
var top = rect.Bottom - height;
return new Rect(left, top, width, height);
}
} }
} }
catch (Exception ex)
{
Logger.Error("Failed top get sinmai window position", ex);
}
return null; return null;
} }
} }

View File

@ -28,57 +28,69 @@ internal class VirtualComPortManager
public async Task InstallComPort() public async Task InstallComPort()
{ {
Logger.Info("Trying to install virtual COM port.");
if (await CheckIfPortInstalled("COM3", false)) if (await CheckIfPortInstalled("COM3", false))
{ {
Logger.Warn("Port COM3 already registered.");
MessageBox.Show("Port COM3 already registered. Either remove it via Device Manager or uninstall the virutal port."); MessageBox.Show("Port COM3 already registered. Either remove it via Device Manager or uninstall the virutal port.");
return; return;
} }
try try
{ {
Logger.Info("Calling com0com to install virtual COM ports");
await ExecuteCommandAsync("setupc.exe", $"install PortName=COM3 PortName=COM23"); await ExecuteCommandAsync("setupc.exe", $"install PortName=COM3 PortName=COM23");
if (await CheckIfPortInstalled("COM3", true)) if (await CheckIfPortInstalled("COM3", true))
{ {
Logger.Info("Port COM3 successfully installed.");
MessageBox.Show("Port COM3 successfully installed."); MessageBox.Show("Port COM3 successfully installed.");
} }
else else
{ {
Logger.Error("Port COM3 failed to install");
MessageBox.Show($"Port COM3 failed to install", "Error", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show($"Port COM3 failed to install", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Error("Port COM3 failed to install", ex);
MessageBox.Show($"Port COM3 failed to install. {ex}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show($"Port COM3 failed to install. {ex}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
} }
} }
public async Task UninstallVirtualPorts() public async Task UninstallVirtualPorts()
{ {
Logger.Info("Trying to uninstall virtual COM port.");
if (!await CheckIfPortInstalled("COM3", true)) if (!await CheckIfPortInstalled("COM3", true))
{ {
Logger.Warn("Port COM3 not found. No need to uninstall.");
MessageBox.Show("Port COM3 not found. No need to uninstall."); MessageBox.Show("Port COM3 not found. No need to uninstall.");
return; return;
} }
try try
{ {
Logger.Info("Calling com0com to uninstall virtual COM ports");
await ExecuteCommandAsync("setupc.exe", $"uninstall"); await ExecuteCommandAsync("setupc.exe", $"uninstall");
if (!await CheckIfPortInstalled("COM3", false)) if (!await CheckIfPortInstalled("COM3", false))
{ {
Logger.Info("Port COM3 successfully uninstalled.");
MessageBox.Show("Port COM3 successfully uninstalled."); MessageBox.Show("Port COM3 successfully uninstalled.");
} }
else else
{ {
Logger.Error("Port COM3 failed to uninstall");
MessageBox.Show($"Port COM3 failed to uninstall. It may be a real device, uninstall it from Device Manager", "Error", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show($"Port COM3 failed to uninstall. It may be a real device, uninstall it from Device Manager", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Error("Port COM3 failed to uninstall", ex);
MessageBox.Show($"Port COM3 failed to uninstall. {ex}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show($"Port COM3 failed to uninstall. {ex}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
} }
} }
private async Task ExecuteCommandAsync(string command, string arguments) private async Task ExecuteCommandAsync(string command, string arguments)
{ {
Logger.Info($"Executing command {command} with arguments {arguments}");
var processStartInfo = new ProcessStartInfo var processStartInfo = new ProcessStartInfo
{ {
FileName = command, FileName = command,
@ -92,5 +104,6 @@ internal class VirtualComPortManager
process.Start(); process.Start();
await process.WaitForExitAsync(); await process.WaitForExitAsync();
Logger.Info($"Command {command} completed");
} }
} }